From 6be9a89195e0d3bf8408cea661f22cb97b638f24 Mon Sep 17 00:00:00 2001 From: Michal Ratajsky Date: Fri, 20 Jun 2014 00:12:40 +0200 Subject: Pulse and API updates, fixes --- backends/null/null-backend.c | 16 +- backends/null/null-backend.h | 6 +- backends/pulse/Makefile.am | 2 + backends/pulse/pulse-backend.c | 354 ++++++++++++++------ backends/pulse/pulse-backend.h | 12 +- backends/pulse/pulse-client-stream.c | 263 ++++++++++++--- backends/pulse/pulse-client-stream.h | 28 +- backends/pulse/pulse-connection.c | 633 ++++++++++++++++++++--------------- backends/pulse/pulse-connection.h | 204 +++++------ backends/pulse/pulse-device.c | 402 ++++++++++++++-------- backends/pulse/pulse-device.h | 3 +- backends/pulse/pulse-helpers.h | 2 +- backends/pulse/pulse-monitor.c | 271 +++++++++++++++ backends/pulse/pulse-monitor.h | 79 +++++ backends/pulse/pulse-sink-input.c | 223 +++++++++--- backends/pulse/pulse-sink-input.h | 21 +- backends/pulse/pulse-sink.c | 246 +++++++++----- backends/pulse/pulse-sink.h | 20 +- backends/pulse/pulse-source-output.c | 198 ++++++++--- backends/pulse/pulse-source-output.h | 12 +- backends/pulse/pulse-source.c | 170 +++++----- backends/pulse/pulse-source.h | 10 +- backends/pulse/pulse-stream.c | 479 +++++++++++++++----------- backends/pulse/pulse-stream.h | 91 ++--- 24 files changed, 2542 insertions(+), 1203 deletions(-) create mode 100644 backends/pulse/pulse-monitor.c create mode 100644 backends/pulse/pulse-monitor.h (limited to 'backends') diff --git a/backends/null/null-backend.c b/backends/null/null-backend.c index f8c22c8..46c5fbf 100644 --- a/backends/null/null-backend.c +++ b/backends/null/null-backend.c @@ -15,11 +15,14 @@ * License along with this library; if not, see . */ +// XXX implement properties from MateMixerBackend + #include #include #include #include +#include #include "null-backend.h" @@ -28,17 +31,17 @@ static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); +static void null_backend_class_init (NullBackendClass *klass); +static void null_backend_class_finalize (NullBackendClass *klass); +static void null_backend_init (NullBackend *null); + G_DEFINE_DYNAMIC_TYPE_EXTENDED (NullBackend, null_backend, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, mate_mixer_backend_interface_init)) -static void null_backend_class_init (NullBackendClass *klass); -static void null_backend_class_finalize (NullBackendClass *klass); -static void null_backend_init (NullBackend *null); - -static gboolean backend_open (MateMixerBackend *backend); -static MateMixerState backend_get_state (MateMixerBackend *backend); +static gboolean backend_open (MateMixerBackend *backend); +static MateMixerState backend_get_state (MateMixerBackend *backend); static MateMixerBackendInfo info; @@ -69,7 +72,6 @@ mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) static void null_backend_class_init (NullBackendClass *klass) { - // XXX is it needed to have this function? shouldn't it call parent method if empty? } /* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ diff --git a/backends/null/null-backend.h b/backends/null/null-backend.h index 2d718e3..ae5f087 100644 --- a/backends/null/null-backend.h +++ b/backends/null/null-backend.h @@ -32,7 +32,7 @@ #define NULL_BACKEND_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), NULL_TYPE_BACKEND, NullBackendClass)) #define NULL_IS_BACKEND_CLASS(k) \ - (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), NULL_TYPE_BACKEND)) + (G_TYPE_CHECK_CLASS_TYPE ((k), NULL_TYPE_BACKEND)) #define NULL_BACKEND_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), NULL_TYPE_BACKEND, NullBackendClass)) @@ -41,14 +41,12 @@ typedef struct _NullBackendClass NullBackendClass; struct _NullBackend { - /*< private >*/ GObject parent; }; struct _NullBackendClass { - /*< private >*/ - GObjectClass parent; + GObjectClass parent_class; }; GType null_backend_get_type (void) G_GNUC_CONST; diff --git a/backends/pulse/Makefile.am b/backends/pulse/Makefile.am index fe0d459..3b632a5 100644 --- a/backends/pulse/Makefile.am +++ b/backends/pulse/Makefile.am @@ -24,6 +24,8 @@ libmatemixer_pulse_la_SOURCES = \ pulse-enum-types.h \ pulse-helpers.c \ pulse-helpers.h \ + pulse-monitor.c \ + pulse-monitor.h \ pulse-stream.c \ pulse-stream.h \ pulse-sink.c \ diff --git a/backends/pulse/pulse-backend.c b/backends/pulse/pulse-backend.c index 79a69a0..8ed7342 100644 --- a/backends/pulse/pulse-backend.c +++ b/backends/pulse/pulse-backend.c @@ -21,6 +21,7 @@ #include #include +#include #include @@ -44,10 +45,11 @@ struct _PulseBackendPrivate gchar *app_version; gchar *app_icon; gchar *server_address; - gchar *default_sink; - gchar *default_source; + gboolean connected_once; + GSource *connect_source; + MateMixerStream *default_sink; + MateMixerStream *default_source; GHashTable *devices; - GHashTable *cards; GHashTable *sinks; GHashTable *sink_inputs; GHashTable *sources; @@ -59,11 +61,25 @@ struct _PulseBackendPrivate enum { PROP_0, PROP_STATE, + PROP_DEFAULT_INPUT, + PROP_DEFAULT_OUTPUT, N_PROPERTIES }; static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); +static void pulse_backend_class_init (PulseBackendClass *klass); +static void pulse_backend_class_finalize (PulseBackendClass *klass); + +static void pulse_backend_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void pulse_backend_init (PulseBackend *pulse); +static void pulse_backend_dispose (GObject *object); +static void pulse_backend_finalize (GObject *object); + G_DEFINE_DYNAMIC_TYPE_EXTENDED (PulseBackend, pulse_backend, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, @@ -127,10 +143,18 @@ static void backend_source_output_removed_cb (PulseConnection guint index, PulseBackend *pulse); +static gboolean backend_try_reconnect (PulseBackend *pulse); +static void backend_remove_connect_source (PulseBackend *pulse); +static void backend_change_state (PulseBackend *backend, + MateMixerState state); + static gint backend_compare_devices (gconstpointer a, gconstpointer b); static gint backend_compare_streams (gconstpointer a, gconstpointer b); +static gboolean backend_compare_stream_name (gpointer key, + gpointer value, + gpointer user_data); static MateMixerBackendInfo info; @@ -166,6 +190,29 @@ mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) iface->set_default_output_stream = backend_set_default_output_stream; } +static void +pulse_backend_class_init (PulseBackendClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = pulse_backend_dispose; + object_class->finalize = pulse_backend_finalize; + object_class->get_property = pulse_backend_get_property; + + g_object_class_override_property (object_class, PROP_STATE, "state"); + g_object_class_override_property (object_class, PROP_DEFAULT_INPUT, "default-input"); + g_object_class_override_property (object_class, PROP_DEFAULT_OUTPUT, "default-output"); + + g_type_class_add_private (object_class, sizeof (PulseBackendPrivate)); +} + +/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ +static void +pulse_backend_class_finalize (PulseBackendClass *klass) +{ +} + static void pulse_backend_get_property (GObject *object, guint param_id, @@ -180,6 +227,12 @@ pulse_backend_get_property (GObject *object, case PROP_STATE: g_value_set_enum (value, pulse->priv->state); break; + case PROP_DEFAULT_INPUT: + g_value_set_object (value, pulse->priv->default_source); + break; + case PROP_DEFAULT_OUTPUT: + g_value_set_object (value, pulse->priv->default_sink); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; @@ -192,16 +245,12 @@ pulse_backend_init (PulseBackend *pulse) pulse->priv = G_TYPE_INSTANCE_GET_PRIVATE (pulse, PULSE_TYPE_BACKEND, PulseBackendPrivate); + pulse->priv->devices = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); - pulse->priv->cards = - g_hash_table_new_full (g_direct_hash, - g_direct_equal, - NULL, - g_object_unref); pulse->priv->sinks = g_hash_table_new_full (g_direct_hash, g_direct_equal, @@ -227,41 +276,7 @@ pulse_backend_init (PulseBackend *pulse) static void pulse_backend_dispose (GObject *object) { - PulseBackend *pulse; - - pulse = PULSE_BACKEND (object); - - if (pulse->priv->devices) { - g_hash_table_destroy (pulse->priv->devices); - pulse->priv->devices = NULL; - } - - if (pulse->priv->cards) { - g_hash_table_destroy (pulse->priv->cards); - pulse->priv->cards = NULL; - } - - if (pulse->priv->sinks) { - g_hash_table_destroy (pulse->priv->sinks); - pulse->priv->devices = NULL; - } - - if (pulse->priv->sink_inputs) { - g_hash_table_destroy (pulse->priv->sink_inputs); - pulse->priv->devices = NULL; - } - - if (pulse->priv->sources) { - g_hash_table_destroy (pulse->priv->sources); - pulse->priv->devices = NULL; - } - - if (pulse->priv->source_outputs) { - g_hash_table_destroy (pulse->priv->source_outputs); - pulse->priv->source_outputs = NULL; - } - - g_clear_object (&pulse->priv->connection); + backend_close (MATE_MIXER_BACKEND (object)); G_OBJECT_CLASS (pulse_backend_parent_class)->dispose (object); } @@ -282,27 +297,6 @@ pulse_backend_finalize (GObject *object) G_OBJECT_CLASS (pulse_backend_parent_class)->finalize (object); } -static void -pulse_backend_class_init (PulseBackendClass *klass) -{ - GObjectClass *object_class; - - object_class = G_OBJECT_CLASS (klass); - object_class->dispose = pulse_backend_dispose; - object_class->finalize = pulse_backend_finalize; - object_class->get_property = pulse_backend_get_property; - - g_object_class_override_property (object_class, PROP_STATE, "state"); - - g_type_class_add_private (klass, sizeof (PulseBackendPrivate)); -} - -/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ -static void -pulse_backend_class_finalize (PulseBackendClass *klass) -{ -} - static gboolean backend_open (MateMixerBackend *backend) { @@ -321,6 +315,10 @@ backend_open (MateMixerBackend *backend) pulse->priv->app_version, pulse->priv->app_icon, pulse->priv->server_address); + + /* No connection attempt is made during the construction of the connection, + * but it sets up the PulseAudio structures, which might fail in an + * unlikely case */ if (G_UNLIKELY (connection == NULL)) { pulse->priv->state = MATE_MIXER_STATE_FAILED; return FALSE; @@ -375,6 +373,8 @@ backend_open (MateMixerBackend *backend) G_CALLBACK (backend_source_output_removed_cb), pulse); + /* Connect to the PulseAudio server, this might fail either instantly or + * asynchronously, for example when remote connection timeouts */ if (!pulse_connection_connect (connection)) { pulse->priv->state = MATE_MIXER_STATE_FAILED; g_object_unref (connection); @@ -388,9 +388,48 @@ backend_open (MateMixerBackend *backend) static void backend_close (MateMixerBackend *backend) { + PulseBackend *pulse; + g_return_if_fail (PULSE_IS_BACKEND (backend)); - g_clear_object (&PULSE_BACKEND (backend)->priv->connection); + pulse = PULSE_BACKEND (backend); + + if (pulse->priv->connection) { + g_signal_handlers_disconnect_by_data (pulse->priv->connection, pulse); + + pulse_connection_disconnect (pulse->priv->connection); + g_clear_object (&pulse->priv->connection); + } + + if (pulse->priv->devices) { + g_hash_table_destroy (pulse->priv->devices); + pulse->priv->devices = NULL; + } + + if (pulse->priv->sinks) { + g_hash_table_destroy (pulse->priv->sinks); + pulse->priv->sinks = NULL; + } + + if (pulse->priv->sink_inputs) { + g_hash_table_destroy (pulse->priv->sink_inputs); + pulse->priv->sink_inputs = NULL; + } + + if (pulse->priv->sources) { + g_hash_table_destroy (pulse->priv->sources); + pulse->priv->sources = NULL; + } + + if (pulse->priv->source_outputs) { + g_hash_table_destroy (pulse->priv->source_outputs); + pulse->priv->source_outputs = NULL; + } + + g_clear_object (&pulse->priv->default_sink); + g_clear_object (&pulse->priv->default_source); + + backend_change_state (pulse, MATE_MIXER_STATE_IDLE); } static MateMixerState @@ -406,18 +445,19 @@ backend_set_data (MateMixerBackend *backend, const MateMixerBackendData *data) { PulseBackend *pulse; - if (data == NULL) - return; - g_return_if_fail (PULSE_IS_BACKEND (backend)); pulse = PULSE_BACKEND (backend); - g_free (data->app_name); - g_free (data->app_id); - g_free (data->app_version); - g_free (data->app_icon); - g_free (data->server_address); + g_clear_pointer (&pulse->priv->app_name, g_free); + g_clear_pointer (&pulse->priv->app_id, g_free); + g_clear_pointer (&pulse->priv->app_version, g_free); + g_clear_pointer (&pulse->priv->app_icon, g_free); + g_clear_pointer (&pulse->priv->server_address, g_free); + + /* Allow to unset the details by passing NULL data */ + if (G_UNLIKELY (data == NULL)) + return; pulse->priv->app_name = g_strdup (data->app_name); pulse->priv->app_id = g_strdup (data->app_id); @@ -433,6 +473,7 @@ backend_list_devices (MateMixerBackend *backend) g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + /* Always create a new current list, caching is done in the main library */ list = g_hash_table_get_values (PULSE_BACKEND (backend)->priv->devices); g_list_foreach (list, (GFunc) g_object_ref, NULL); @@ -450,6 +491,7 @@ backend_list_streams (MateMixerBackend *backend) pulse = PULSE_BACKEND (backend); + /* Always create a new current list, caching is done in the main library */ list = g_list_concat (g_hash_table_get_values (pulse->priv->sinks), g_hash_table_get_values (pulse->priv->sink_inputs)); list = g_list_concat (list, @@ -465,7 +507,9 @@ backend_list_streams (MateMixerBackend *backend) static MateMixerStream * backend_get_default_input_stream (MateMixerBackend *backend) { - return NULL; + g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + + return PULSE_BACKEND (backend)->priv->default_source; } static gboolean @@ -473,8 +517,8 @@ backend_set_default_input_stream (MateMixerBackend *backend, MateMixerStream *st { PulseBackend *pulse; - g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); - g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); + g_return_val_if_fail (PULSE_IS_BACKEND (backend), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); pulse = PULSE_BACKEND (backend); @@ -485,7 +529,9 @@ backend_set_default_input_stream (MateMixerBackend *backend, MateMixerStream *st static MateMixerStream * backend_get_default_output_stream (MateMixerBackend *backend) { - return NULL; + g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + + return PULSE_BACKEND (backend)->priv->default_sink; } static gboolean @@ -493,8 +539,8 @@ backend_set_default_output_stream (MateMixerBackend *backend, MateMixerStream *s { PulseBackend *pulse; - g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); - g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); + g_return_val_if_fail (PULSE_IS_BACKEND (backend), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); pulse = PULSE_BACKEND (backend); @@ -511,17 +557,39 @@ backend_connection_state_cb (PulseConnection *connection, switch (state) { case PULSE_CONNECTION_DISCONNECTED: + if (pulse->priv->connected_once) { + /* We managed to connect once before, try to reconnect and if it + * fails immediately, use an idle source; + * in case the idle source already exists, just let it try again */ + if (!pulse->priv->connect_source && + !pulse_connection_connect (connection)) { + pulse->priv->connect_source = g_idle_source_new (); + + g_source_set_callback (pulse->priv->connect_source, + (GSourceFunc) backend_try_reconnect, + pulse, + (GDestroyNotify) backend_remove_connect_source); + + g_source_attach (pulse->priv->connect_source, + g_main_context_get_thread_default ()); + } + break; + } + + /* First connection attempt has failed */ + backend_change_state (pulse, MATE_MIXER_STATE_FAILED); break; + case PULSE_CONNECTION_CONNECTING: - break; case PULSE_CONNECTION_AUTHORIZING: - break; case PULSE_CONNECTION_LOADING: + backend_change_state (pulse, MATE_MIXER_STATE_CONNECTING); break; + case PULSE_CONNECTION_CONNECTED: - pulse->priv->state = MATE_MIXER_STATE_READY; + pulse->priv->connected_once = TRUE; - g_object_notify (G_OBJECT (pulse), "state"); + backend_change_state (pulse, MATE_MIXER_STATE_READY); break; } } @@ -531,21 +599,64 @@ backend_server_info_cb (PulseConnection *connection, const pa_server_info *info, PulseBackend *pulse) { - // XXX add property - - if (g_strcmp0 (pulse->priv->default_sink, info->default_sink_name)) { - g_free (pulse->priv->default_sink); - - pulse->priv->default_sink = g_strdup (info->default_sink_name); - // g_object_notify (G_OBJECT (pulse), "default-output"); + const gchar *name_source = NULL; + const gchar *name_sink = NULL; + + if (pulse->priv->default_source) + name_source = mate_mixer_stream_get_name (pulse->priv->default_source); + if (pulse->priv->default_sink) + name_sink = mate_mixer_stream_get_name (pulse->priv->default_sink); + + if (g_strcmp0 (name_source, info->default_source_name)) { + if (pulse->priv->default_source) + g_clear_object (&pulse->priv->default_source); + + if (info->default_source_name != NULL) { + MateMixerStream *stream = g_hash_table_find (pulse->priv->sources, + backend_compare_stream_name, + (gpointer) info->default_source_name); + + /* It is theoretically possible to receive a server info notification + * before the stream lists are fully downloaded, this should not be + * a problem as a newer notification will arrive later after the + * streams are read. + * Of course this will only work if Pulse delivers notifications in + * the correct order, but it seems it does. */ + if (G_LIKELY (stream != NULL)) { + pulse->priv->default_source = g_object_ref (stream); + g_debug ("Default input stream changed to %s", info->default_source_name); + + g_object_notify (G_OBJECT (pulse), "default-output"); + } else + g_debug ("Default input stream %s not yet known", + info->default_source_name); + } } - if (g_strcmp0 (pulse->priv->default_source, info->default_source_name)) { - g_free (pulse->priv->default_source); - - pulse->priv->default_source = g_strdup (info->default_source_name); - // g_object_notify (G_OBJECT (pulse), "default-input"); + if (g_strcmp0 (name_sink, info->default_sink_name)) { + if (pulse->priv->default_sink) + g_clear_object (&pulse->priv->default_sink); + + if (info->default_sink_name != NULL) { + MateMixerStream *stream = g_hash_table_find (pulse->priv->sinks, + backend_compare_stream_name, + (gpointer) info->default_sink_name); + if (G_LIKELY (stream != NULL)) { + pulse->priv->default_sink = g_object_ref (stream); + g_debug ("Default output stream changed to %s", info->default_sink_name); + + g_object_notify (G_OBJECT (pulse), "default-output"); + } else + g_debug ("Default output stream %s not yet known", + info->default_sink_name); + } } + + if (pulse->priv->state != MATE_MIXER_STATE_READY) + g_debug ("Sound server is %s version %s, running on %s", + info->server_name, + info->server_version, + info->host_name); } static void @@ -557,12 +668,12 @@ backend_card_info_cb (PulseConnection *connection, p = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (info->index)); if (!p) { - PulseDevice *device; + PulseDevice *device = pulse_device_new (connection, info); - device = pulse_device_new (connection, info); g_hash_table_insert (pulse->priv->devices, GINT_TO_POINTER (pulse_device_get_index (device)), device); + g_signal_emit_by_name (G_OBJECT (pulse), "device-added", mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); @@ -606,9 +717,8 @@ backend_sink_info_cb (PulseConnection *connection, p = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (info->index)); if (!p) { - PulseStream *stream; + PulseStream *stream = pulse_sink_new (connection, info); - stream = pulse_sink_new (connection, info); g_hash_table_insert (pulse->priv->sinks, GINT_TO_POINTER (pulse_stream_get_index (stream)), stream); @@ -653,12 +763,15 @@ backend_sink_input_info_cb (PulseConnection *connection, PulseBackend *pulse) { gpointer p; + gpointer parent; + + parent = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (info->sink)); p = g_hash_table_lookup (pulse->priv->sink_inputs, GINT_TO_POINTER (info->index)); if (!p) { PulseStream *stream; - stream = pulse_sink_input_new (connection, info); + stream = pulse_sink_input_new (connection, info, parent); g_hash_table_insert (pulse->priv->sink_inputs, GINT_TO_POINTER (pulse_stream_get_index (stream)), stream); @@ -667,7 +780,7 @@ backend_sink_input_info_cb (PulseConnection *connection, "stream-added", mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); } else { - pulse_sink_input_update (p, info); + pulse_sink_input_update (p, info, parent); g_signal_emit_by_name (G_OBJECT (pulse), "stream-changed", @@ -708,6 +821,9 @@ backend_source_info_cb (PulseConnection *connection, if (!p) { PulseStream *stream; + if (info->monitor_of_sink != PA_INVALID_INDEX) + return; + stream = pulse_source_new (connection, info); g_hash_table_insert (pulse->priv->sources, GINT_TO_POINTER (pulse_stream_get_index (stream)), @@ -733,6 +849,8 @@ backend_source_removed_cb (PulseConnection *connection, gpointer p; gchar *name; + // XXX set parent + p = g_hash_table_lookup (pulse->priv->sources, GINT_TO_POINTER (index)); if (G_UNLIKELY (p == NULL)) return; @@ -797,6 +915,32 @@ backend_source_output_removed_cb (PulseConnection *connection, g_free (name); } +static gboolean +backend_try_reconnect (PulseBackend *pulse) +{ + /* When the connect call succeeds, return FALSE to remove the idle source + * and wait for the connection state notifications, otherwise this function + * will be called again */ + return !pulse_connection_connect (pulse->priv->connection); +} + +static void +backend_remove_connect_source (PulseBackend *pulse) +{ + g_clear_pointer (&pulse->priv->connect_source, g_source_unref); +} + +static void +backend_change_state (PulseBackend *backend, MateMixerState state) +{ + if (backend->priv->state == state) + return; + + backend->priv->state = state; + + g_object_notify (G_OBJECT (backend), "state"); +} + static gint backend_compare_devices (gconstpointer a, gconstpointer b) { @@ -810,3 +954,11 @@ backend_compare_streams (gconstpointer a, gconstpointer b) return strcmp (mate_mixer_stream_get_name (MATE_MIXER_STREAM (a)), mate_mixer_stream_get_name (MATE_MIXER_STREAM (b))); } + +static gboolean +backend_compare_stream_name (gpointer key, gpointer value, gpointer user_data) +{ + MateMixerStream *stream = MATE_MIXER_STREAM (value); + + return !strcmp (mate_mixer_stream_get_name (stream), (const gchar *) user_data); +} diff --git a/backends/pulse/pulse-backend.h b/backends/pulse/pulse-backend.h index 64be9b7..813d359 100644 --- a/backends/pulse/pulse-backend.h +++ b/backends/pulse/pulse-backend.h @@ -21,6 +21,8 @@ #include #include +#include + #define PULSE_TYPE_BACKEND \ (pulse_backend_get_type ()) #define PULSE_BACKEND(o) \ @@ -30,7 +32,7 @@ #define PULSE_BACKEND_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_BACKEND, PulseBackendClass)) #define PULSE_IS_BACKEND_CLASS(k) \ - (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), PULSE_TYPE_BACKEND)) + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_BACKEND)) #define PULSE_BACKEND_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_BACKEND, PulseBackendClass)) @@ -40,15 +42,15 @@ typedef struct _PulseBackendPrivate PulseBackendPrivate; struct _PulseBackend { + GObject parent; + /*< private >*/ - GObject parent; - PulseBackendPrivate *priv; + PulseBackendPrivate *priv; }; struct _PulseBackendClass { - /*< private >*/ - GObjectClass parent; + GObjectClass parent_class; }; GType pulse_backend_get_type (void) G_GNUC_CONST; diff --git a/backends/pulse/pulse-client-stream.c b/backends/pulse/pulse-client-stream.c index ebeec99..a597e69 100644 --- a/backends/pulse/pulse-client-stream.c +++ b/backends/pulse/pulse-client-stream.c @@ -20,14 +20,18 @@ #include #include +#include #include #include "pulse-client-stream.h" -#include "pulse-stream.h" struct _PulseClientStreamPrivate { + gchar *app_name; + gchar *app_id; + gchar *app_version; + gchar *app_icon; MateMixerStream *parent; }; @@ -35,30 +39,69 @@ enum { PROP_0, PROP_PARENT, + PROP_APP_NAME, + PROP_APP_ID, + PROP_APP_VERSION, + PROP_APP_ICON, N_PROPERTIES }; -static void mate_mixer_client_stream_interface_init (MateMixerClientStreamInterface *iface); -static void pulse_client_stream_class_init (PulseClientStreamClass *klass); -static void pulse_client_stream_init (PulseClientStream *client); -static void pulse_client_stream_dispose (GObject *object); +static void mate_mixer_client_stream_interface_init (MateMixerClientStreamInterface *iface); -/* Interface implementation */ -static MateMixerStream *stream_client_get_parent (MateMixerClientStream *client); -static gboolean stream_client_set_parent (MateMixerClientStream *client, - MateMixerStream *parent); -static gboolean stream_client_remove (MateMixerClientStream *client); +static void pulse_client_stream_class_init (PulseClientStreamClass *klass); + +static void pulse_client_stream_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void pulse_client_stream_init (PulseClientStream *client); +static void pulse_client_stream_dispose (GObject *object); +static void pulse_client_stream_finalize (GObject *object); G_DEFINE_ABSTRACT_TYPE_WITH_CODE (PulseClientStream, pulse_client_stream, PULSE_TYPE_STREAM, 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 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 void mate_mixer_client_stream_interface_init (MateMixerClientStreamInterface *iface) { - iface->get_parent = stream_client_get_parent; - iface->set_parent = stream_client_set_parent; - iface->remove = stream_client_remove; + 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; +} + +static void +pulse_client_stream_class_init (PulseClientStreamClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = pulse_client_stream_dispose; + object_class->finalize = pulse_client_stream_finalize; + object_class->get_property = pulse_client_stream_get_property; + + 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"); + g_object_class_override_property (object_class, PROP_APP_VERSION, "app-version"); + g_object_class_override_property (object_class, PROP_APP_ICON, "app-icon"); + + g_type_class_add_private (object_class, sizeof (PulseClientStreamPrivate)); } static void @@ -75,34 +118,24 @@ pulse_client_stream_get_property (GObject *object, case PROP_PARENT: g_value_set_object (value, client->priv->parent); break; + case PROP_APP_NAME: + g_value_set_string (value, client->priv->app_name); + break; + case PROP_APP_ID: + g_value_set_string (value, client->priv->app_id); + break; + case PROP_APP_VERSION: + g_value_set_string (value, client->priv->app_version); + break; + case PROP_APP_ICON: + g_value_set_string (value, client->priv->app_icon); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } -static void -pulse_client_stream_class_init (PulseClientStreamClass *klass) -{ - GObjectClass *object_class; - - object_class = G_OBJECT_CLASS (klass); - object_class->dispose = pulse_client_stream_dispose; - object_class->get_property = pulse_client_stream_get_property; - - g_object_class_install_property (object_class, - PROP_PARENT, - g_param_spec_object ("parent", - "Parent", - "Parent stream of the client stream", - MATE_MIXER_TYPE_STREAM, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS)); - - g_type_class_add_private (object_class, sizeof (PulseClientStreamPrivate)); -} - static void pulse_client_stream_init (PulseClientStream *client) { @@ -123,8 +156,120 @@ pulse_client_stream_dispose (GObject *object) G_OBJECT_CLASS (pulse_client_stream_parent_class)->dispose (object); } +static void +pulse_client_stream_finalize (GObject *object) +{ + PulseClientStream *client; + + client = PULSE_CLIENT_STREAM (object); + + g_free (client->priv->app_name); + g_free (client->priv->app_id); + g_free (client->priv->app_version); + g_free (client->priv->app_icon); + + G_OBJECT_CLASS (pulse_client_stream_parent_class)->finalize (object); +} + +gboolean +pulse_client_stream_update_parent (MateMixerClientStream *client, + MateMixerStream *parent) +{ + PulseClientStream *pulse; + + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + + pulse = PULSE_CLIENT_STREAM (client); + + if (pulse->priv->parent != parent) { + g_clear_object (&pulse->priv->parent); + + if (G_LIKELY (parent != NULL)) + pulse->priv->parent = g_object_ref (parent); + + g_object_notify (G_OBJECT (client), "parent"); + } + return TRUE; +} + +gboolean +pulse_client_stream_update_app_name (MateMixerClientStream *client, + const gchar *app_name) +{ + PulseClientStream *pulse; + + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + + pulse = PULSE_CLIENT_STREAM (client); + + if (g_strcmp0 (pulse->priv->app_name, app_name)) { + g_free (pulse->priv->app_name); + pulse->priv->app_name = g_strdup (app_name); + + g_object_notify (G_OBJECT (client), "app-name"); + } + return TRUE; +} + +gboolean +pulse_client_stream_update_app_id (MateMixerClientStream *client, + const gchar *app_id) +{ + PulseClientStream *pulse; + + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + + pulse = PULSE_CLIENT_STREAM (client); + + if (g_strcmp0 (pulse->priv->app_id, app_id)) { + g_free (pulse->priv->app_id); + pulse->priv->app_id = g_strdup (app_id); + + g_object_notify (G_OBJECT (client), "app-id"); + } + return TRUE; +} + +gboolean +pulse_client_stream_update_app_version (MateMixerClientStream *client, + const gchar *app_version) +{ + PulseClientStream *pulse; + + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + + pulse = PULSE_CLIENT_STREAM (client); + + if (g_strcmp0 (pulse->priv->app_version, app_version)) { + g_free (pulse->priv->app_version); + pulse->priv->app_version = g_strdup (app_version); + + g_object_notify (G_OBJECT (client), "app-version"); + } + return TRUE; +} + +gboolean +pulse_client_stream_update_app_icon (MateMixerClientStream *client, + const gchar *app_icon) +{ + PulseClientStream *pulse; + + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + + pulse = PULSE_CLIENT_STREAM (client); + + if (g_strcmp0 (pulse->priv->app_icon, app_icon)) { + g_free (pulse->priv->app_icon); + pulse->priv->app_icon = g_strdup (app_icon); + + g_object_notify (G_OBJECT (client), "app-icon"); + } + return TRUE; +} + static MateMixerStream * -stream_client_get_parent (MateMixerClientStream *client) +client_stream_get_parent (MateMixerClientStream *client) { g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); @@ -132,15 +277,49 @@ stream_client_get_parent (MateMixerClientStream *client) } static gboolean -stream_client_set_parent (MateMixerClientStream *client, MateMixerStream *parent) +client_stream_set_parent (MateMixerClientStream *client, MateMixerStream *parent) { - // TODO - return TRUE; + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + + return PULSE_CLIENT_STREAM_GET_CLASS (client)->set_parent (client, parent); } static gboolean -stream_client_remove (MateMixerClientStream *client) +client_stream_remove (MateMixerClientStream *client) { - // TODO - return TRUE; + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + + return PULSE_CLIENT_STREAM_GET_CLASS (client)->remove (client); +} + +static const gchar * +client_stream_get_app_name (MateMixerClientStream *client) +{ + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); + + return PULSE_CLIENT_STREAM (client)->priv->app_name; +} + +static const gchar * +client_stream_get_app_id (MateMixerClientStream *client) +{ + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); + + return PULSE_CLIENT_STREAM (client)->priv->app_id; +} + +static const gchar * +client_stream_get_app_version (MateMixerClientStream *client) +{ + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); + + return PULSE_CLIENT_STREAM (client)->priv->app_version; +} + +static const gchar * +client_stream_get_app_icon (MateMixerClientStream *client) +{ + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); + + return PULSE_CLIENT_STREAM (client)->priv->app_icon; } diff --git a/backends/pulse/pulse-client-stream.h b/backends/pulse/pulse-client-stream.h index cf801ce..c24a535 100644 --- a/backends/pulse/pulse-client-stream.h +++ b/backends/pulse/pulse-client-stream.h @@ -22,8 +22,7 @@ #include #include - -#include +#include #include "pulse-stream.h" @@ -38,7 +37,7 @@ G_BEGIN_DECLS #define PULSE_CLIENT_STREAM_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_CLIENT_STREAM, PulseClientStreamClass)) #define PULSE_IS_CLIENT_STREAM_CLASS(k) \ - (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), PULSE_TYPE_CLIENT_STREAM)) + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_CLIENT_STREAM)) #define PULSE_CLIENT_STREAM_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_CLIENT_STREAM, PulseClientStreamClass)) @@ -50,19 +49,32 @@ struct _PulseClientStream { PulseStream parent; + /*< private >*/ PulseClientStreamPrivate *priv; }; struct _PulseClientStreamClass { - PulseStreamClass parent; + PulseStreamClass parent_class; - gboolean (*set_parent) (MateMixerClientStream *client, - MateMixerStream *stream); - gboolean (*remove) (MateMixerClientStream *client); + gboolean (*set_parent) (MateMixerClientStream *client, + MateMixerStream *stream); + gboolean (*remove) (MateMixerClientStream *client); }; -GType pulse_client_stream_get_type (void) G_GNUC_CONST; +GType pulse_client_stream_get_type (void) G_GNUC_CONST; + +gboolean pulse_client_stream_update_parent (MateMixerClientStream *client, + MateMixerStream *parent); + +gboolean pulse_client_stream_update_app_name (MateMixerClientStream *client, + const gchar *app_name); +gboolean pulse_client_stream_update_app_id (MateMixerClientStream *client, + const gchar *app_id); +gboolean pulse_client_stream_update_app_version (MateMixerClientStream *client, + const gchar *app_version); +gboolean pulse_client_stream_update_app_icon (MateMixerClientStream *client, + const gchar *app_icon); G_END_DECLS diff --git a/backends/pulse/pulse-connection.c b/backends/pulse/pulse-connection.c index ec172ca..4289660 100644 --- a/backends/pulse/pulse-connection.c +++ b/backends/pulse/pulse-connection.c @@ -26,14 +26,14 @@ #include "pulse-connection.h" #include "pulse-enums.h" #include "pulse-enum-types.h" +#include "pulse-monitor.h" struct _PulseConnectionPrivate { gchar *server; guint outstanding; - gboolean reconnect; - gboolean connected_once; pa_context *context; + pa_proplist *proplist; pa_glib_mainloop *mainloop; PulseConnectionState state; }; @@ -41,11 +41,12 @@ struct _PulseConnectionPrivate enum { PROP_0, PROP_SERVER, - PROP_RECONNECT, PROP_STATE, N_PROPERTIES }; +static GParamSpec *properties[N_PROPERTIES] = { NULL, }; + enum { SERVER_INFO, CARD_INFO, @@ -61,55 +62,229 @@ enum { N_SIGNALS }; -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_list_loaded (PulseConnection *connection); -static gboolean connection_process_operation (PulseConnection *connection, - pa_operation *op); +static guint signals[N_SIGNALS] = { 0, }; -G_DEFINE_TYPE (PulseConnection, pulse_connection, G_TYPE_OBJECT); +static void pulse_connection_class_init (PulseConnectionClass *klass); -static GParamSpec *properties[N_PROPERTIES] = { NULL, }; +static void pulse_connection_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void pulse_connection_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); -static guint signals[N_SIGNALS] = { 0, }; +static void pulse_connection_init (PulseConnection *connection); +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 void -pulse_connection_init (PulseConnection *connection) +pulse_connection_class_init (PulseConnectionClass *klass) { - connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (connection, - PULSE_TYPE_CONNECTION, - PulseConnectionPrivate); + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = pulse_connection_finalize; + object_class->get_property = pulse_connection_get_property; + object_class->set_property = pulse_connection_set_property; + + properties[PROP_SERVER] = + g_param_spec_string ("server", + "Server", + "PulseAudio server to connect to", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_STATE] = + g_param_spec_enum ("state", + "State", + "Connection state", + PULSE_TYPE_CONNECTION_STATE, + PULSE_CONNECTION_DISCONNECTED, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + signals[SERVER_INFO] = + g_signal_new ("server-info", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, server_info), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[CARD_INFO] = + g_signal_new ("card-info", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, card_info), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[CARD_REMOVED] = + g_signal_new ("card-removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, card_removed), + NULL, + NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + signals[SINK_INFO] = + g_signal_new ("sink-info", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, sink_info), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[SINK_REMOVED] = + g_signal_new ("sink-removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, sink_removed), + NULL, + NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + signals[SINK_INPUT_INFO] = + g_signal_new ("sink-input-info", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, sink_input_info), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[SINK_INPUT_REMOVED] = + g_signal_new ("sink-input-removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, sink_input_removed), + NULL, + NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + signals[SOURCE_INFO] = + g_signal_new ("source-info", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, source_info), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[SOURCE_REMOVED] = + g_signal_new ("source-removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, source_removed), + NULL, + NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + signals[SOURCE_OUTPUT_INFO] = + g_signal_new ("source-output-info", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, source_output_info), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[SOURCE_OUTPUT_REMOVED] = + g_signal_new ("source-output-removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, source_output_removed), + NULL, + NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + g_type_class_add_private (object_class, sizeof (PulseConnectionPrivate)); } static void @@ -126,9 +301,6 @@ pulse_connection_get_property (GObject *object, case PROP_SERVER: g_value_set_string (value, connection->priv->server); break; - case PROP_RECONNECT: - g_value_set_boolean (value, connection->priv->reconnect); - break; case PROP_STATE: g_value_set_enum (value, connection->priv->state); break; @@ -150,12 +322,8 @@ pulse_connection_set_property (GObject *object, switch (param_id) { case PROP_SERVER: - g_free (connection->priv->server); - - connection->priv->server = g_value_dup_string (value); - break; - case PROP_RECONNECT: - connection->priv->reconnect = g_value_get_boolean (value); + /* Construct-only string */ + connection->priv->server = g_strdup (g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); @@ -163,6 +331,14 @@ pulse_connection_set_property (GObject *object, } } +static void +pulse_connection_init (PulseConnection *connection) +{ + connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (connection, + PULSE_TYPE_CONNECTION, + PulseConnectionPrivate); +} + static void pulse_connection_finalize (GObject *object) { @@ -172,165 +348,12 @@ pulse_connection_finalize (GObject *object) g_free (connection->priv->server); - G_OBJECT_CLASS (pulse_connection_parent_class)->finalize (object); -} + pa_context_unref (connection->priv->context); + pa_proplist_free (connection->priv->proplist); -static void -pulse_connection_class_init (PulseConnectionClass *klass) -{ - GObjectClass *object_class; + pa_glib_mainloop_free (connection->priv->mainloop); - object_class = G_OBJECT_CLASS (klass); - object_class->finalize = pulse_connection_finalize; - object_class->get_property = pulse_connection_get_property; - object_class->set_property = pulse_connection_set_property; - - properties[PROP_SERVER] = g_param_spec_string ("server", - "Server", - "PulseAudio server to connect to", - NULL, - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - - properties[PROP_RECONNECT] = g_param_spec_boolean ("reconnect", - "Reconnect", - "Try to reconnect when connection is lost", - TRUE, - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - - properties[PROP_STATE] = g_param_spec_enum ("state", - "State", - "Connection state", - PULSE_TYPE_CONNECTION_STATE, - PULSE_CONNECTION_DISCONNECTED, - G_PARAM_READABLE | - G_PARAM_STATIC_STRINGS); - - signals[SERVER_INFO] = g_signal_new ("server-info", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseConnectionClass, server_info), - NULL, - NULL, - g_cclosure_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, - G_TYPE_POINTER); - - signals[CARD_INFO] = g_signal_new ("card-info", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseConnectionClass, card_removed), - NULL, - NULL, - g_cclosure_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, - G_TYPE_POINTER); - - signals[CARD_REMOVED] = g_signal_new ("card-removed", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseConnectionClass, card_removed), - NULL, - NULL, - g_cclosure_marshal_VOID__UINT, - G_TYPE_NONE, - 1, - G_TYPE_UINT); - - signals[SINK_INFO] = g_signal_new ("sink-info", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseConnectionClass, sink_info), - NULL, - NULL, - g_cclosure_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, - G_TYPE_POINTER); - - signals[SINK_REMOVED] = g_signal_new ("sink-removed", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseConnectionClass, sink_removed), - NULL, - NULL, - g_cclosure_marshal_VOID__UINT, - G_TYPE_NONE, - 1, - G_TYPE_UINT); - - signals[SINK_INPUT_INFO] = g_signal_new ("sink-input-info", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseConnectionClass, sink_input_info), - NULL, - NULL, - g_cclosure_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, - G_TYPE_POINTER); - - signals[SINK_INPUT_REMOVED] = g_signal_new ("sink-input-removed", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseConnectionClass, sink_input_removed), - NULL, - NULL, - g_cclosure_marshal_VOID__UINT, - G_TYPE_NONE, - 1, - G_TYPE_UINT); - - signals[SOURCE_INFO] = g_signal_new ("source-info", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseConnectionClass, source_info), - NULL, - NULL, - g_cclosure_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, - G_TYPE_POINTER); - - signals[SOURCE_REMOVED] = g_signal_new ("source-removed", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseConnectionClass, source_removed), - NULL, - NULL, - g_cclosure_marshal_VOID__UINT, - G_TYPE_NONE, - 1, - G_TYPE_UINT); - - signals[SOURCE_OUTPUT_INFO] = g_signal_new ("source-output-info", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseConnectionClass, source_output_info), - NULL, - NULL, - g_cclosure_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, - G_TYPE_POINTER); - - signals[SOURCE_OUTPUT_REMOVED] = g_signal_new ("source-output-removed", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (PulseConnectionClass, source_output_removed), - NULL, - NULL, - g_cclosure_marshal_VOID__UINT, - G_TYPE_NONE, - 1, - G_TYPE_UINT); - - g_object_class_install_properties (object_class, N_PROPERTIES, properties); - - g_type_class_add_private (object_class, sizeof (PulseConnectionPrivate)); + G_OBJECT_CLASS (pulse_connection_parent_class)->finalize (object); } PulseConnection * @@ -351,6 +374,9 @@ pulse_connection_new (const gchar *app_name, return NULL; } + /* Create a property list to hold information about the application, + * the list will be kept with the connection as it may be reused later + * when creating PulseAudio streams */ proplist = pa_proplist_new (); if (app_name) pa_proplist_sets (proplist, PA_PROP_APPLICATION_NAME, app_name); @@ -366,80 +392,90 @@ pulse_connection_new (const gchar *app_name, app_name, proplist); } else { + /* Try to set some sensible default name when application does not + * provide a name */ gchar *name = connection_get_app_name (); context = pa_context_new_with_proplist (pa_glib_mainloop_get_api (mainloop), name, proplist); - g_free (name); } - pa_proplist_free (proplist); if (G_UNLIKELY (context == NULL)) { g_warning ("Failed to create PulseAudio context"); + pa_glib_mainloop_free (mainloop); + pa_proplist_free (proplist); return NULL; } connection = g_object_new (PULSE_TYPE_CONNECTION, "server", server_address, - "reconnect", TRUE, NULL); + /* Set function to monitor status changes */ + pa_context_set_state_callback (context, + connection_state_cb, + connection); + connection->priv->mainloop = mainloop; connection->priv->context = context; + connection->priv->proplist = proplist; + return connection; } gboolean pulse_connection_connect (PulseConnection *connection) { - int ret; - g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (connection->priv->state != PULSE_CONNECTION_DISCONNECTED) return TRUE; - /* Set function to monitor status changes */ - pa_context_set_state_callback (connection->priv->context, - connection_state_cb, - connection); - - /* Initiate a connection, this call does not guarantee the connection - * to be established and usable */ - ret = pa_context_connect (connection->priv->context, NULL, PA_CONTEXT_NOFLAGS, NULL); - if (ret < 0) { - g_warning ("Failed to connect to PulseAudio server: %s", pa_strerror (ret)); + /* Initiate a connection, state changes will be delivered asynchronously */ + if (pa_context_connect (connection->priv->context, + connection->priv->server, + PA_CONTEXT_NOFLAGS, + NULL) == 0) + return TRUE; + else return FALSE; - } - return TRUE; } void pulse_connection_disconnect (PulseConnection *connection) { - g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + g_return_if_fail (PULSE_IS_CONNECTION (connection)); if (connection->priv->state == PULSE_CONNECTION_DISCONNECTED) return; pa_context_disconnect (connection->priv->context); - connection->priv->state = PULSE_CONNECTION_DISCONNECTED; - - g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_STATE]); + connection_change_state (connection, PULSE_CONNECTION_DISCONNECTED); } PulseConnectionState pulse_connection_get_state (PulseConnection *connection) { - g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), PULSE_CONNECTION_DISCONNECTED); return connection->priv->state; } +PulseMonitor * +pulse_connection_create_monitor (PulseConnection *connection, + guint32 index_source, + guint32 index_sink_input) +{ + return pulse_monitor_new (connection->priv->context, + connection->priv->proplist, + index_source, + index_sink_input); +} + gboolean pulse_connection_set_default_sink (PulseConnection *connection, const gchar *name) @@ -665,6 +701,40 @@ pulse_connection_set_source_output_volume (PulseConnection *connection, #endif } +gboolean +pulse_connection_suspend_sink (PulseConnection *connection, + guint32 index, + gboolean suspend) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + + op = pa_context_suspend_sink_by_index (connection->priv->context, + index, + (int) suspend, + NULL, NULL); + + return connection_process_operation (connection, op); +} + +gboolean +pulse_connection_suspend_source (PulseConnection *connection, + guint32 index, + gboolean suspend) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + + op = pa_context_suspend_source_by_index (connection->priv->context, + index, + (int) suspend, + NULL, NULL); + + return connection_process_operation (connection, op); +} + gboolean pulse_connection_move_sink_input (PulseConnection *connection, guint32 index, @@ -755,19 +825,10 @@ connection_load_lists (PulseConnection *connection) g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); if (G_UNLIKELY (connection->priv->outstanding)) { - /* This can only mean a bug */ g_warn_if_reached (); return FALSE; } - op = pa_context_get_server_info (connection->priv->context, - connection_server_info_cb, - connection); - if (G_UNLIKELY (op == NULL)) - goto error; - - ops = g_list_prepend (ops, op); - op = pa_context_get_card_info_list (connection->priv->context, connection_card_info_cb, connection); @@ -834,9 +895,16 @@ connection_state_cb (pa_context *c, void *userdata) if (state == PA_CONTEXT_READY) { pa_operation *op; - // XXX check state + if (connection->priv->state == PULSE_CONNECTION_LOADING || + connection->priv->state == PULSE_CONNECTION_CONNECTED) { + g_warn_if_reached (); + return; + } + /* We are connected, let's subscribe to notifications and load the + * initial lists */ op = pa_context_subscribe (connection->priv->context, + PA_SUBSCRIPTION_MASK_SERVER | PA_SUBSCRIPTION_MASK_CARD | PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | @@ -844,25 +912,22 @@ connection_state_cb (pa_context *c, void *userdata) PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, NULL, NULL); if (op) { - connection->priv->state = PULSE_CONNECTION_LOADING; - connection->priv->connected_once = TRUE; - pa_context_set_subscribe_callback (connection->priv->context, connection_subscribe_cb, connection); pa_operation_unref (op); connection_load_lists (connection); + connection_change_state (connection, PULSE_CONNECTION_LOADING); + return; + } - g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_STATE]); - } else { - /* If we could not subscribe to notifications, we consider it the - * same as a connection failture */ - g_warning ("Failed to subscribe to PulseAudio notifications: %s", - pa_strerror (pa_context_errno (connection->priv->context))); + /* If we could not subscribe to notifications, we consider it the + * same as a connection failture */ + g_warning ("Failed to subscribe to PulseAudio notifications: %s", + pa_strerror (pa_context_errno (connection->priv->context))); - state = PA_CONTEXT_FAILED; - } + state = PA_CONTEXT_FAILED; } if (state == PA_CONTEXT_TERMINATED || state == PA_CONTEXT_FAILED) { @@ -870,10 +935,14 @@ connection_state_cb (pa_context *c, void *userdata) * change which should not normally happen, because the signal subscription * is cancelled before disconnecting */ pulse_connection_disconnect (connection); - - if (connection->priv->connected_once && connection->priv->reconnect) - pulse_connection_connect (connection); + return; } + + if (state == PA_CONTEXT_CONNECTING) + connection_change_state (connection, PULSE_CONNECTION_CONNECTING); + else if (state == PA_CONTEXT_AUTHORIZING || + state == PA_CONTEXT_SETTING_NAME) + connection_change_state (connection, PULSE_CONNECTION_AUTHORIZING); } static void @@ -970,10 +1039,19 @@ connection_server_info_cb (pa_context *c, const pa_server_info *info, void *userdata) { - g_signal_emit (G_OBJECT (userdata), + PulseConnection *connection; + + connection = PULSE_CONNECTION (userdata); + + g_signal_emit (G_OBJECT (connection), signals[SERVER_INFO], 0, info); + + /* 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); } static void @@ -1008,13 +1086,16 @@ connection_sink_info_cb (pa_context *c, connection = PULSE_CONNECTION (userdata); - if (eol) - connection_list_loaded (connection); - else - g_signal_emit (G_OBJECT (connection), - signals[SINK_INFO], - 0, - info); + if (eol) { + if (connection->priv->state == PULSE_CONNECTION_LOADING) + connection_list_loaded (connection); + return; + } + + g_signal_emit (G_OBJECT (connection), + signals[SINK_INFO], + 0, + info); } static void @@ -1083,6 +1164,17 @@ connection_source_output_info_cb (pa_context *c, info); } +static void +connection_change_state (PulseConnection *connection, PulseConnectionState state) +{ + if (connection->priv->state == state) + return; + + connection->priv->state = state; + + g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_STATE]); +} + static void connection_list_loaded (PulseConnection *connection) { @@ -1093,9 +1185,14 @@ connection_list_loaded (PulseConnection *connection) connection->priv->outstanding = 0; } if (connection->priv->outstanding == 0) { - connection->priv->state = PULSE_CONNECTION_CONNECTED; + pa_operation *op; + + op = pa_context_get_server_info (connection->priv->context, + connection_server_info_cb, + connection); - g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_STATE]); + if (G_UNLIKELY (!connection_process_operation (connection, op))) + pulse_connection_disconnect (connection); } } diff --git a/backends/pulse/pulse-connection.h b/backends/pulse/pulse-connection.h index 922b65a..b3f01b7 100644 --- a/backends/pulse/pulse-connection.h +++ b/backends/pulse/pulse-connection.h @@ -24,6 +24,7 @@ #include #include "pulse-enums.h" +#include "pulse-monitor.h" G_BEGIN_DECLS @@ -36,7 +37,7 @@ G_BEGIN_DECLS #define PULSE_CONNECTION_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_CONNECTION, PulseConnectionClass)) #define PULSE_IS_CONNECTION_CLASS(k) \ - (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), PULSE_TYPE_CONNECTION)) + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_CONNECTION)) #define PULSE_CONNECTION_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_CONNECTION, PulseConnectionClass)) @@ -48,110 +49,117 @@ struct _PulseConnection { GObject parent; + /*< private >*/ PulseConnectionPrivate *priv; }; struct _PulseConnectionClass { - GObjectClass parent; - - 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); + GObjectClass parent_class; + + /* 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); }; -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); -void pulse_connection_disconnect (PulseConnection *connection); - -PulseConnectionState pulse_connection_get_state (PulseConnection *connection); - -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_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); +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); +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); G_END_DECLS diff --git a/backends/pulse/pulse-device.c b/backends/pulse/pulse-device.c index ad17f21..d15972e 100644 --- a/backends/pulse/pulse-device.c +++ b/backends/pulse/pulse-device.c @@ -17,8 +17,10 @@ #include #include +#include #include +#include #include #include @@ -26,6 +28,7 @@ #include "pulse-connection.h" #include "pulse-device.h" +#include "pulse-stream.h" struct _PulseDevicePrivate { @@ -34,6 +37,8 @@ struct _PulseDevicePrivate gchar *description; GList *profiles; GList *ports; + GList *streams; + gboolean streams_sorted; gchar *icon; PulseConnection *connection; MateMixerProfile *profile; @@ -45,36 +50,60 @@ enum PROP_NAME, PROP_DESCRIPTION, PROP_ICON, + PROP_PORTS, + PROP_PROFILES, PROP_ACTIVE_PROFILE, + PROP_INDEX, + PROP_CONNECTION, N_PROPERTIES }; static void mate_mixer_device_interface_init (MateMixerDeviceInterface *iface); -static const gchar * device_get_name (MateMixerDevice *device); -static const gchar * device_get_description (MateMixerDevice *device); -static const gchar * device_get_icon (MateMixerDevice *device); +static void pulse_device_class_init (PulseDeviceClass *klass); -static const GList * device_list_streams (MateMixerDevice *device); +static void pulse_device_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void pulse_device_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); -static const GList * device_list_ports (MateMixerDevice *device); -static const GList * device_list_profiles (MateMixerDevice *device); -static MateMixerProfile *device_get_active_profile (MateMixerDevice *device); - -static gboolean device_set_active_profile (MateMixerDevice *device, - const gchar *name); +static void pulse_device_init (PulseDevice *device); +static void pulse_device_dispose (GObject *object); +static void pulse_device_finalize (GObject *object); 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); + +static const GList * device_list_ports (MateMixerDevice *device); +static const GList * device_list_profiles (MateMixerDevice *device); + +static MateMixerProfile *device_get_active_profile (MateMixerDevice *device); +static gboolean device_set_active_profile (MateMixerDevice *device, + const gchar *profile); + +static gint device_compare_ports (gconstpointer a, + gconstpointer b); +static gint device_compare_profiles (gconstpointer a, + gconstpointer b); + +static void device_free_ports (PulseDevice *device); +static void device_free_profiles (PulseDevice *device); + 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_streams = device_list_streams; iface->list_ports = device_list_ports; iface->list_profiles = device_list_profiles; iface->get_active_profile = device_get_active_profile; @@ -82,11 +111,45 @@ mate_mixer_device_interface_init (MateMixerDeviceInterface *iface) } static void -pulse_device_init (PulseDevice *device) +pulse_device_class_init (PulseDeviceClass *klass) { - device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device, - PULSE_TYPE_DEVICE, - PulseDevicePrivate); + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = pulse_device_dispose; + object_class->finalize = pulse_device_finalize; + object_class->get_property = pulse_device_get_property; + object_class->set_property = pulse_device_set_property; + + g_object_class_install_property (object_class, + PROP_INDEX, + g_param_spec_uint ("index", + "Index", + "Device 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_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)); } static void @@ -109,9 +172,21 @@ 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; + case PROP_INDEX: + g_value_set_uint (value, device->priv->index); + break; + case PROP_CONNECTION: + g_value_set_object (value, device->priv->connection); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; @@ -129,14 +204,12 @@ pulse_device_set_property (GObject *object, device = PULSE_DEVICE (object); switch (param_id) { - case PROP_NAME: - device->priv->name = g_strdup (g_value_get_string (value)); - break; - case PROP_DESCRIPTION: - device->priv->description = g_strdup (g_value_get_string (value)); + case PROP_INDEX: + device->priv->index = g_value_get_uint (value); break; - case PROP_ICON: - device->priv->icon = g_strdup (g_value_get_string (value)); + case PROP_CONNECTION: + /* Construct-only object property */ + device->priv->connection = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); @@ -144,6 +217,14 @@ pulse_device_set_property (GObject *object, } } +static void +pulse_device_init (PulseDevice *device) +{ + device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device, + PULSE_TYPE_DEVICE, + PulseDevicePrivate); +} + static void pulse_device_dispose (GObject *object) { @@ -151,17 +232,9 @@ pulse_device_dispose (GObject *object) device = PULSE_DEVICE (object); - if (device->priv->profiles != NULL) { - g_list_free_full (device->priv->profiles, g_object_unref); - device->priv->profiles = NULL; - } + device_free_ports (device); + device_free_profiles (device); - if (device->priv->ports != NULL) { - g_list_free_full (device->priv->ports, g_object_unref); - device->priv->ports = NULL; - } - - g_clear_object (&device->priv->profile); g_clear_object (&device->priv->connection); G_OBJECT_CLASS (pulse_device_parent_class)->dispose (object); @@ -181,46 +254,113 @@ pulse_device_finalize (GObject *object) G_OBJECT_CLASS (pulse_device_parent_class)->finalize (object); } -static void -pulse_device_class_init (PulseDeviceClass *klass) +PulseDevice * +pulse_device_new (PulseConnection *connection, const pa_card_info *info) { - GObjectClass *object_class; + PulseDevice *device; - object_class = G_OBJECT_CLASS (klass); - object_class->dispose = pulse_device_dispose; - object_class->finalize = pulse_device_finalize; - object_class->get_property = pulse_device_get_property; - object_class->set_property = pulse_device_set_property; + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (info != NULL, NULL); - 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_ACTIVE_PROFILE, "active-profile"); + /* Consider the device index as unchanging parameter */ + device = g_object_new (PULSE_TYPE_DEVICE, + "connection", connection, + "index", info->index, + NULL); - g_type_class_add_private (object_class, sizeof (PulseDevicePrivate)); + /* Other data may change at any time, so let's make a use of our update function */ + pulse_device_update (device, info); + + return device; } -PulseDevice * -pulse_device_new (PulseConnection *connection, const pa_card_info *info) +gboolean +pulse_device_update (PulseDevice *device, const pa_card_info *info) { - PulseDevice *device; - MateMixerProfile *active_profile = NULL; - GList *profiles = NULL; - GList *ports = NULL; - guint32 i; + const gchar *prop; + guint32 i; - g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (PULSE_IS_DEVICE (device), FALSE); + g_return_val_if_fail (info != NULL, FALSE); + + /* Let all the information update before emitting notify signals */ + g_object_freeze_notify (G_OBJECT (device)); + + /* Name */ + if (g_strcmp0 (device->priv->name, info->name)) { + g_free (device->priv->name); + device->priv->name = g_strdup (info->name); + + g_object_notify (G_OBJECT (device), "name"); + } + + /* Description */ + prop = pa_proplist_gets (info->proplist, PA_PROP_DEVICE_DESCRIPTION); + + if (G_UNLIKELY (prop == NULL)) + prop = info->name; + + if (g_strcmp0 (device->priv->description, prop)) { + g_free (device->priv->description); + device->priv->description = g_strdup (prop); + + g_object_notify (G_OBJECT (device), "description"); + } + + /* Icon */ + prop = pa_proplist_gets (info->proplist, PA_PROP_DEVICE_ICON_NAME); + + if (G_UNLIKELY (prop == NULL)) + prop = "audio-card"; + + if (g_strcmp0 (device->priv->icon, prop)) { + g_free (device->priv->icon); + device->priv->icon = g_strdup (prop); + + g_object_notify (G_OBJECT (device), "icon"); + } + + /* 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)); + } + device->priv->ports = g_list_sort (device->priv->ports, device_compare_ports); + + g_object_notify (G_OBJECT (device), "ports"); + + /* List of profiles */ + device_free_profiles (device); - /* Create a list of card profiles */ for (i = 0; i < info->n_profiles; i++) { MateMixerProfile *profile; #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 differs in the new available flag, we use it not to include - * profiles which are unavailable */ + /* PulseAudio 5.0 includes a new pa_card_profile_info2 which only + * differs in the new available flag, we use it not to include profiles + * which are unavailable */ if (p_info->available == 0) continue; #else @@ -232,78 +372,23 @@ pulse_device_new (PulseConnection *connection, const pa_card_info *info) p_info->description, p_info->priority); + if (device->priv->profile == NULL) { #if PA_CHECK_VERSION(5, 0, 0) - if (!g_strcmp0 (p_info->name, info->active_profile2->name)) - active_profile = g_object_ref (profile); + if (!g_strcmp0 (p_info->name, info->active_profile2->name)) + device->priv->profile = g_object_ref (profile); #else - if (!g_strcmp0 (p_info->name, info->active_profile->name)) - active_profile = g_object_ref (profile); + if (!g_strcmp0 (p_info->name, info->active_profile->name)) + device->priv->profile = g_object_ref (profile); #endif - profiles = g_list_prepend (profiles, profile); - } - - /* Keep the profiles in the same order as in PulseAudio */ - if (profiles) - profiles = g_list_reverse (profiles); - - /* Create a list of card ports */ - for (i = 0; i < info->n_ports; i++) { - MateMixerPort *port; - MateMixerPortStatus status = MATE_MIXER_PORT_UNKNOWN_STATUS; - pa_card_port_info *p_info = info->ports[i]; - -#if PA_CHECK_VERSION(2, 0, 0) - switch (p_info->available) { - case PA_PORT_AVAILABLE_YES: - status = MATE_MIXER_PORT_AVAILABLE; - break; - case PA_PORT_AVAILABLE_NO: - status = MATE_MIXER_PORT_UNAVAILABLE; - break; - default: - break; } -#endif - port = mate_mixer_port_new (p_info->name, - p_info->description, - pa_proplist_gets (p_info->proplist, "device.icon_name"), - p_info->priority, - status); - - ports = g_list_prepend (ports, port); + device->priv->profiles = g_list_prepend (device->priv->profiles, profile); } + device->priv->profiles = g_list_sort (device->priv->profiles, + device_compare_profiles); - /* Keep the ports in the same order as in PulseAudio */ - if (ports) - ports = g_list_reverse (ports); - - device = g_object_new (PULSE_TYPE_DEVICE, - "name", info->name, - "description", pa_proplist_gets (info->proplist, "device.description"), - "icon", pa_proplist_gets (info->proplist, "device.icon_name"), - NULL); + g_object_notify (G_OBJECT (device), "profiles"); - if (profiles) { - device->priv->profiles = profiles; - - if (G_LIKELY (active_profile)) - device->priv->profile = g_object_ref (active_profile); - } - - device->priv->index = info->index; - device->priv->ports = ports; - device->priv->connection = g_object_ref (connection); - - return device; -} - -gboolean -pulse_device_update (PulseDevice *device, const pa_card_info *info) -{ - g_return_val_if_fail (PULSE_IS_DEVICE (device), FALSE); - g_return_val_if_fail (info != NULL, FALSE); - - // TODO: update status, active_profile, maybe others? + g_object_thaw_notify (G_OBJECT (device)); return TRUE; } @@ -347,13 +432,6 @@ device_get_icon (MateMixerDevice *device) return PULSE_DEVICE (device)->priv->icon; } -static const GList * -device_list_streams (MateMixerDevice *device) -{ - // TODO - return NULL; -} - static const GList * device_list_ports (MateMixerDevice *device) { @@ -379,16 +457,66 @@ device_get_active_profile (MateMixerDevice *device) } static gboolean -device_set_active_profile (MateMixerDevice *device, const gchar *name) +device_set_active_profile (MateMixerDevice *device, const gchar *profile) { - PulseDevicePrivate *priv; - g_return_val_if_fail (PULSE_IS_DEVICE (device), FALSE); - g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (profile != NULL, FALSE); - priv = PULSE_DEVICE (device)->priv; + return pulse_connection_set_card_profile (PULSE_DEVICE (device)->priv->connection, + PULSE_DEVICE (device)->priv->name, + profile); +} + +static gint +device_compare_ports (gconstpointer a, gconstpointer b) +{ + MateMixerPort *p1 = MATE_MIXER_PORT (a); + MateMixerPort *p2 = MATE_MIXER_PORT (b); + + gint ret = (gint) (mate_mixer_port_get_priority (p2) - + mate_mixer_port_get_priority (p1)); + if (ret != 0) + return ret; + else + return strcmp (mate_mixer_port_get_name (p1), + mate_mixer_port_get_name (p2)); +} + +static gint +device_compare_profiles (gconstpointer a, gconstpointer b) +{ + MateMixerProfile *p1 = MATE_MIXER_PROFILE (a); + MateMixerProfile *p2 = MATE_MIXER_PROFILE (b); + + gint ret = (gint) (mate_mixer_profile_get_priority (p2) - + mate_mixer_profile_get_priority (p1)); + if (ret != 0) + return ret; + else + return strcmp (mate_mixer_profile_get_name (p1), + mate_mixer_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); - return pulse_connection_set_card_profile (priv->connection, - priv->name, - name); + device->priv->profiles = NULL; } diff --git a/backends/pulse/pulse-device.h b/backends/pulse/pulse-device.h index b862879..94c331f 100644 --- a/backends/pulse/pulse-device.h +++ b/backends/pulse/pulse-device.h @@ -36,7 +36,7 @@ G_BEGIN_DECLS #define PULSE_DEVICE_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_DEVICE, PulseDeviceClass)) #define PULSE_IS_DEVICE_CLASS(k) \ - (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), PULSE_TYPE_DEVICE)) + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_DEVICE)) #define PULSE_DEVICE_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_IS_DEVICE, PulseDeviceClass)) @@ -48,6 +48,7 @@ struct _PulseDevice { GObject parent; + /*< private >*/ PulseDevicePrivate *priv; }; diff --git a/backends/pulse/pulse-helpers.h b/backends/pulse/pulse-helpers.h index 36cc0c1..978fd38 100644 --- a/backends/pulse/pulse-helpers.h +++ b/backends/pulse/pulse-helpers.h @@ -25,7 +25,7 @@ #include - G_BEGIN_DECLS +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); diff --git a/backends/pulse/pulse-monitor.c b/backends/pulse/pulse-monitor.c new file mode 100644 index 0000000..21613d0 --- /dev/null +++ b/backends/pulse/pulse-monitor.c @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2014 Michal Ratajsky + * + * 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 . + */ + +#include +#include + +#include + +#include "pulse-monitor.h" + +struct _PulseMonitorPrivate +{ + pa_context *context; + pa_proplist *proplist; + pa_stream *stream; + guint32 index_source; + guint32 index_sink_input; + gboolean enabled; +}; + +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); + +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 void +pulse_monitor_class_init (PulseMonitorClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = pulse_monitor_finalize; + + signals[VALUE] = + g_signal_new ("value", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseMonitorClass, value), + NULL, + NULL, + g_cclosure_marshal_VOID__DOUBLE, + G_TYPE_NONE, + 1, + G_TYPE_DOUBLE); + + g_type_class_add_private (object_class, sizeof (PulseMonitorPrivate)); +} + +static void +pulse_monitor_init (PulseMonitor *monitor) +{ + monitor->priv = G_TYPE_INSTANCE_GET_PRIVATE (monitor, + PULSE_TYPE_MONITOR, + PulseMonitorPrivate); +} + +static void +pulse_monitor_finalize (GObject *object) +{ + PulseMonitor *monitor; + + monitor = PULSE_MONITOR (object); + + if (monitor->priv->stream) + pa_stream_unref (monitor->priv->stream); + + pa_context_unref (monitor->priv->context); + pa_proplist_free (monitor->priv->proplist); + + G_OBJECT_CLASS (pulse_monitor_parent_class)->finalize (object); +} + +PulseMonitor * +pulse_monitor_new (pa_context *context, + pa_proplist *proplist, + guint32 index_source, + guint32 index_sink_input) +{ + PulseMonitor *monitor; + + monitor = g_object_new (PULSE_TYPE_MONITOR, 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); + + monitor->priv->enabled = FALSE; +} + +gboolean +pulse_monitor_is_enabled (PulseMonitor *monitor) +{ + g_return_if_fail (PULSE_IS_MONITOR (monitor)); + + return monitor->priv->enabled; +} + +gboolean +pulse_monitor_update_index (PulseMonitor *monitor, + guint32 index_source, + guint32 index_sink_input) +{ + 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) + 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); + + /* Unset the Pulse stream to let enable recreate it */ + g_clear_pointer (&monitor->priv->stream, pa_stream_unref); + + 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); + } + return TRUE; +} + +static gboolean +monitor_prepare (PulseMonitor *monitor) +{ + pa_sample_spec spec; + + spec.channels = 1; + spec.format = PA_SAMPLE_FLOAT32; + spec.rate = 25; + + monitor->priv->stream = + pa_stream_new_with_proplist (monitor->priv->context, "Peak detect", + &spec, + NULL, + monitor->priv->proplist); + + if (G_UNLIKELY (monitor->priv->stream == NULL)) { + g_warning ("Failed to create PulseAudio monitor: %s", + pa_strerror (pa_context_errno (monitor->priv->context))); + return FALSE; + } + + 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, + 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); + + if (ret < 0) { + g_warning ("Failed to connect PulseAudio monitor: %s", pa_strerror (ret)); + return FALSE; + } + return TRUE; +} + +static void +monitor_read_cb (pa_stream *stream, size_t length, void *userdata) +{ + const void *data; + int ret; + + ret = pa_stream_peek (stream, &data, &length); + if (ret < 0) { + g_debug ("Failed to read PulseAudio stream data: %s", pa_strerror (ret)); + return; + } + + if (data) { + gdouble v = ((const gfloat *) data)[length / sizeof (gfloat) - 1]; + + g_signal_emit (G_OBJECT (userdata), + signals[VALUE], + 0, + CLAMP (v, 0, 1)); + } + + /* pa_stream_drop() should not be called if the buffer is empty, but it + * should be called if there is a hole */ + if (length) + pa_stream_drop (stream); +} diff --git a/backends/pulse/pulse-monitor.h b/backends/pulse/pulse-monitor.h new file mode 100644 index 0000000..d82d5cf --- /dev/null +++ b/backends/pulse/pulse-monitor.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014 Michal Ratajsky + * + * 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 . + */ + +#ifndef PULSE_MONITOR_H +#define PULSE_MONITOR_H + +#include +#include + +#include + +G_BEGIN_DECLS + +#define PULSE_TYPE_MONITOR \ + (pulse_monitor_get_type ()) +#define PULSE_MONITOR(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_MONITOR, PulseMonitor)) +#define PULSE_IS_MONITOR(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_MONITOR)) +#define PULSE_MONITOR_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_MONITOR, PulseMonitorClass)) +#define PULSE_IS_MONITOR_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_MONITOR)) +#define PULSE_MONITOR_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_MONITOR, PulseMonitorClass)) + +typedef struct _PulseMonitor PulseMonitor; +typedef struct _PulseMonitorClass PulseMonitorClass; +typedef struct _PulseMonitorPrivate PulseMonitorPrivate; + +struct _PulseMonitor +{ + GObject parent; + + /*< private >*/ + PulseMonitorPrivate *priv; +}; + +struct _PulseMonitorClass +{ + GObjectClass parent_class; + + /* Signals */ + void (*value) (PulseMonitor *monitor, + gdouble value); +}; + +GType pulse_monitor_get_type (void) G_GNUC_CONST; + +PulseMonitor *pulse_monitor_new (pa_context *context, + pa_proplist *proplist, + 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_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 8540193..74734dc 100644 --- a/backends/pulse/pulse-sink-input.c +++ b/backends/pulse/pulse-sink-input.c @@ -15,41 +15,35 @@ * License along with this library; if not, see . */ +#include #include #include #include +#include #include #include "pulse-connection.h" #include "pulse-client-stream.h" +#include "pulse-monitor.h" +#include "pulse-sink.h" #include "pulse-sink-input.h" #include "pulse-stream.h" -struct _PulseSinkInputPrivate -{ - guint32 index_monitor; -}; - -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 void pulse_sink_input_class_init (PulseSinkInputClass *klass); +static void pulse_sink_input_init (PulseSinkInput *input); G_DEFINE_TYPE (PulseSinkInput, pulse_sink_input, PULSE_TYPE_CLIENT_STREAM); -static void -pulse_sink_input_init (PulseSinkInput *input) -{ - input->priv = G_TYPE_INSTANCE_GET_PRIVATE (input, - PULSE_TYPE_SINK_INPUT, - PulseSinkInputPrivate); -} +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_class_init (PulseSinkInputClass *klass) @@ -59,22 +53,31 @@ 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->set_mute = sink_input_set_mute; + stream_class->set_volume = sink_input_set_volume; + stream_class->create_monitor = 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; +} - g_type_class_add_private (klass, sizeof (PulseSinkInputPrivate)); +static void +pulse_sink_input_init (PulseSinkInput *input) +{ } PulseStream * -pulse_sink_input_new (PulseConnection *connection, const pa_sink_input_info *info) +pulse_sink_input_new (PulseConnection *connection, + const pa_sink_input_info *info, + PulseStream *parent) { PulseSinkInput *input; + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (info != NULL, NULL); + /* Consider the sink input index as unchanging parameter */ input = g_object_new (PULSE_TYPE_SINK_INPUT, "connection", connection, @@ -82,35 +85,56 @@ pulse_sink_input_new (PulseConnection *connection, const pa_sink_input_info *inf NULL); /* Other data may change at any time, so let's make a use of our update function */ - pulse_sink_input_update (PULSE_STREAM (input), info); + pulse_sink_input_update (PULSE_STREAM (input), info, parent); return PULSE_STREAM (input); } gboolean -pulse_sink_input_update (PulseStream *stream, const pa_sink_input_info *info) +pulse_sink_input_update (PulseStream *stream, + const pa_sink_input_info *info, + PulseStream *parent) { - MateMixerStreamFlags flags = MATE_MIXER_STREAM_OUTPUT | - MATE_MIXER_STREAM_CLIENT | - MATE_MIXER_STREAM_HAS_MUTE; + MateMixerStreamFlags flags = MATE_MIXER_STREAM_OUTPUT | + MATE_MIXER_STREAM_CLIENT | + MATE_MIXER_STREAM_HAS_MUTE | + MATE_MIXER_STREAM_HAS_MONITOR; + gchar *name; + + const gchar *prop; + const gchar *description = NULL; g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), 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); - pulse_stream_update_channel_map (stream, &info->channel_map); + /* 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. */ + name = g_strdup_printf ("pulse-stream-client-output-%lu", (gulong) info->index); - /* Build the flag list */ - if (info->has_volume) { - flags |= MATE_MIXER_STREAM_HAS_VOLUME; - pulse_stream_update_volume (stream, &info->volume); + pulse_stream_update_name (stream, name); + g_free (name); + + prop = pa_proplist_gets (info->proplist, PA_PROP_MEDIA_ROLE); + + 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 (G_LIKELY (prop != NULL)) + description = prop; + + flags |= MATE_MIXER_STREAM_EVENT; } - if (info->volume_writable) - flags |= MATE_MIXER_STREAM_CAN_SET_VOLUME; + if (description == NULL) + description = info->name; + + pulse_stream_update_description (stream, description); + pulse_stream_update_mute (stream, info->mute ? TRUE : FALSE); if (info->client != PA_INVALID_INDEX) flags |= MATE_MIXER_STREAM_APPLICATION; @@ -120,8 +144,56 @@ pulse_sink_input_update (PulseStream *stream, const pa_sink_input_info *info) if (pa_channel_map_can_fade (&info->channel_map)) flags |= MATE_MIXER_STREAM_CAN_FADE; +#if PA_CHECK_VERSION(1, 0, 0) + 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; + + /* Flags needed before volume */ pulse_stream_update_flags (stream, flags); + if (info->has_volume) + pulse_stream_update_volume (stream, &info->volume, &info->channel_map); + else + pulse_stream_update_volume (stream, NULL, &info->channel_map); +#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 */ + flags |= + MATE_MIXER_STREAM_HAS_VOLUME | + MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME | + MATE_MIXER_STREAM_CAN_SET_VOLUME; + + /* Flags needed before volume */ + pulse_stream_update_flags (stream, flags); + + pulse_stream_update_volume (stream, &info->volume, &info->channel_map); +#endif + + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_NAME); + if (prop != NULL) + pulse_client_stream_update_app_name (MATE_MIXER_CLIENT_STREAM (stream), prop); + + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ID); + if (prop != NULL) + pulse_client_stream_update_app_id (MATE_MIXER_CLIENT_STREAM (stream), prop); + + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_VERSION); + if (prop != NULL) + pulse_client_stream_update_app_version (MATE_MIXER_CLIENT_STREAM (stream), prop); + + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ICON_NAME); + if (prop != NULL) + pulse_client_stream_update_app_icon (MATE_MIXER_CLIENT_STREAM (stream), prop); + + pulse_client_stream_update_parent (MATE_MIXER_CLIENT_STREAM (stream), + MATE_MIXER_STREAM (parent)); + + // XXX needs to fix monitor if parent changes + g_object_thaw_notify (G_OBJECT (stream)); return TRUE; } @@ -129,42 +201,91 @@ pulse_sink_input_update (PulseStream *stream, const pa_sink_input_info *info) static gboolean sink_input_set_mute (MateMixerStream *stream, gboolean mute) { - PulseStream *ps; + PulseStream *pulse; g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), FALSE); - ps = PULSE_STREAM (stream); + pulse = PULSE_STREAM (stream); - return pulse_connection_set_sink_input_mute (pulse_stream_get_connection (ps), - pulse_stream_get_index (ps), + return pulse_connection_set_sink_input_mute (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), mute); } static gboolean sink_input_set_volume (MateMixerStream *stream, pa_cvolume *volume) { - PulseStream *ps; + PulseStream *pulse; g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), FALSE); g_return_val_if_fail (volume != NULL, FALSE); - ps = PULSE_STREAM (stream); + pulse = PULSE_STREAM (stream); - return pulse_connection_set_sink_input_volume (pulse_stream_get_connection (ps), - pulse_stream_get_index (ps), + return pulse_connection_set_sink_input_volume (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), volume); } static gboolean sink_input_set_parent (MateMixerClientStream *stream, MateMixerStream *parent) { - // TODO - return TRUE; + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), FALSE); + + if (G_UNLIKELY (!PULSE_IS_SINK (parent))) { + g_warning ("Could not change stream parent to %s: not a parent output stream", + mate_mixer_stream_get_name (parent)); + return FALSE; + } + + pulse = PULSE_STREAM (stream); + + return pulse_connection_move_sink_input (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), + pulse_stream_get_index (PULSE_STREAM (parent))); } static gboolean sink_input_remove (MateMixerClientStream *stream) { - // TODO - return TRUE; + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), FALSE); + + pulse = PULSE_STREAM (stream); + + return pulse_connection_kill_sink_input (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse)); +} + +static PulseMonitor * +sink_input_create_monitor (MateMixerStream *stream) +{ + MateMixerStream *parent; + PulseStream *pulse; + guint32 index; + + g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), NULL); + + parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (stream)); + 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)); + 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)); + return NULL; + } + + return pulse_connection_create_monitor (pulse_stream_get_connection (pulse), + index, + pulse_stream_get_index (pulse)); } diff --git a/backends/pulse/pulse-sink-input.h b/backends/pulse/pulse-sink-input.h index 110ca9c..a498999 100644 --- a/backends/pulse/pulse-sink-input.h +++ b/backends/pulse/pulse-sink-input.h @@ -21,11 +21,11 @@ #include #include -#include - #include #include "pulse-client-stream.h" +#include "pulse-connection.h" +#include "pulse-stream.h" G_BEGIN_DECLS @@ -38,33 +38,32 @@ G_BEGIN_DECLS #define PULSE_SINK_INPUT_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_SINK_INPUT, PulseSinkInputClass)) #define PULSE_IS_SINK_INPUT_CLASS(k) \ - (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SINK_INPUT)) + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SINK_INPUT)) #define PULSE_SINK_INPUT_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SINK_INPUT, PulseSinkInputClass)) -typedef struct _PulseSinkInput PulseSinkInput; -typedef struct _PulseSinkInputClass PulseSinkInputClass; -typedef struct _PulseSinkInputPrivate PulseSinkInputPrivate; +typedef struct _PulseSinkInput PulseSinkInput; +typedef struct _PulseSinkInputClass PulseSinkInputClass; struct _PulseSinkInput { PulseClientStream parent; - - PulseSinkInputPrivate *priv; }; struct _PulseSinkInputClass { - PulseClientStreamClass parent; + PulseClientStreamClass parent_class; }; GType pulse_sink_input_get_type (void) G_GNUC_CONST; PulseStream *pulse_sink_input_new (PulseConnection *connection, - const pa_sink_input_info *info); + const pa_sink_input_info *info, + PulseStream *parent); gboolean pulse_sink_input_update (PulseStream *stream, - const pa_sink_input_info *info); + const pa_sink_input_info *info, + PulseStream *parent); G_END_DECLS diff --git a/backends/pulse/pulse-sink.c b/backends/pulse/pulse-sink.c index 53ed64f..b7a440b 100644 --- a/backends/pulse/pulse-sink.c +++ b/backends/pulse/pulse-sink.c @@ -19,11 +19,11 @@ #include #include -#include #include #include "pulse-connection.h" +#include "pulse-monitor.h" #include "pulse-stream.h" #include "pulse-sink.h" @@ -32,27 +32,20 @@ struct _PulseSinkPrivate guint32 index_monitor; }; -enum { - PROP_0, - N_PROPERTIES -}; - -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_name); +static void pulse_sink_class_init (PulseSinkClass *klass); +static void pulse_sink_init (PulseSink *sink); G_DEFINE_TYPE (PulseSink, pulse_sink, PULSE_TYPE_STREAM); -static void -pulse_sink_init (PulseSink *sink) -{ - sink->priv = G_TYPE_INSTANCE_GET_PRIVATE (sink, - PULSE_TYPE_SINK, - PulseSinkPrivate); -} +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_class_init (PulseSinkClass *klass) @@ -64,43 +57,30 @@ pulse_sink_class_init (PulseSinkClass *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; g_type_class_add_private (klass, sizeof (PulseSinkPrivate)); } +static void +pulse_sink_init (PulseSink *sink) +{ + sink->priv = G_TYPE_INSTANCE_GET_PRIVATE (sink, + PULSE_TYPE_SINK, + PulseSinkPrivate); + + sink->priv->index_monitor = PA_INVALID_INDEX; +} + PulseStream * pulse_sink_new (PulseConnection *connection, const pa_sink_info *info) { PulseSink *sink; - GList *ports = NULL; - int i; - - /* Convert the list of sink ports to a GList of MateMixerPorts */ - for (i = 0; i < info->n_ports; i++) { - MateMixerPort *port; - MateMixerPortStatus status = MATE_MIXER_PORT_UNKNOWN_STATUS; - pa_sink_port_info *p_info = info->ports[i]; - -#if PA_CHECK_VERSION(2, 0, 0) - switch (p_info->available) { - case PA_PORT_AVAILABLE_YES: - status = MATE_MIXER_PORT_AVAILABLE; - break; - case PA_PORT_AVAILABLE_NO: - status = MATE_MIXER_PORT_UNAVAILABLE; - break; - default: - break; - } -#endif - port = mate_mixer_port_new (p_info->name, - p_info->description, - NULL, - p_info->priority, - status); - ports = g_list_prepend (ports, port); - } + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (info != NULL, NULL); /* Consider the sink index as unchanging parameter */ sink = g_object_new (PULSE_TYPE_SINK, @@ -108,58 +88,78 @@ pulse_sink_new (PulseConnection *connection, const pa_sink_info *info) "index", info->index, NULL); - /* According to the PulseAudio code, the list of sink port never changes with - * updates. - * This may be not future-proof, but checking and validating the list of ports on - * each update would be an expensive operation, so let's set the list only during - * the construction */ - pulse_stream_update_ports (PULSE_STREAM (sink), g_list_reverse (ports)); - /* Other data may change at any time, so let's make a use of our update function */ pulse_sink_update (PULSE_STREAM (sink), info); return PULSE_STREAM (sink); } +guint32 +pulse_sink_get_monitor_index (PulseStream *stream) +{ + g_return_val_if_fail (PULSE_IS_SINK (stream), PA_INVALID_INDEX); + + return PULSE_SINK (stream)->priv->index_monitor; +} + gboolean pulse_sink_update (PulseStream *stream, const pa_sink_info *info) { - PulseSink *sink; - MateMixerStreamFlags flags = MATE_MIXER_STREAM_OUTPUT | - MATE_MIXER_STREAM_HAS_MUTE | - MATE_MIXER_STREAM_HAS_VOLUME | - MATE_MIXER_STREAM_CAN_SET_VOLUME; + 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); - sink = PULSE_SINK (stream); - /* 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); - pulse_stream_update_channel_map (stream, &info->channel_map); - pulse_stream_update_volume_extended (stream, - &info->volume, - info->base_volume, - info->n_volume_steps); + + /* 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); + /* Stream state */ switch (info->state) { case PA_SINK_RUNNING: - pulse_stream_update_status (stream, MATE_MIXER_STREAM_RUNNING); + pulse_stream_update_state (stream, MATE_MIXER_STREAM_RUNNING); break; case PA_SINK_IDLE: - pulse_stream_update_status (stream, MATE_MIXER_STREAM_IDLE); + pulse_stream_update_state (stream, MATE_MIXER_STREAM_IDLE); break; case PA_SINK_SUSPENDED: - pulse_stream_update_status (stream, MATE_MIXER_STREAM_SUSPENDED); + pulse_stream_update_state (stream, MATE_MIXER_STREAM_SUSPENDED); break; default: - pulse_stream_update_status (stream, MATE_MIXER_STREAM_UNKNOWN_STATUS); + pulse_stream_update_state (stream, MATE_MIXER_STREAM_UNKNOWN_STATE); break; } @@ -169,21 +169,35 @@ pulse_sink_update (PulseStream *stream, const pa_sink_info *info) if (info->flags & PA_SINK_FLAT_VOLUME) flags |= MATE_MIXER_STREAM_HAS_FLAT_VOLUME; - if (info->monitor_source_name) - flags |= MATE_MIXER_STREAM_OUTPUT_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; + /* Flags must be updated before volume */ pulse_stream_update_flags (stream, flags); + pulse_stream_update_volume_extended (stream, + &info->volume, + &info->channel_map, + info->base_volume, + info->n_volume_steps); + + sink = PULSE_SINK (stream); + + /* Handle change of monitoring source index */ + // XXX probably call this each time to validate if (sink->priv->index_monitor != info->monitor_source) { - sink->priv->index_monitor = info->monitor_source; + PulseMonitor *monitor; + + monitor = pulse_stream_get_monitor (PULSE_STREAM (stream)); - // TODO: provide a property - // g_object_notify (G_OBJECT (stream), "monitor"); + if (monitor) + pulse_monitor_update_index (monitor, + info->monitor_source, + PA_INVALID_INDEX); + + sink->priv->index_monitor = info->monitor_source; } g_object_thaw_notify (G_OBJECT (stream)); @@ -193,31 +207,93 @@ pulse_sink_update (PulseStream *stream, const pa_sink_info *info) static gboolean sink_set_mute (MateMixerStream *stream, gboolean mute) { + PulseStream *pulse; + g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); - return pulse_connection_set_sink_mute (pulse_stream_get_connection (PULSE_STREAM (stream)), - pulse_stream_get_index (PULSE_STREAM (stream)), + pulse = PULSE_STREAM (stream); + + return pulse_connection_set_sink_mute (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), mute); } static gboolean sink_set_volume (MateMixerStream *stream, pa_cvolume *volume) { + PulseStream *pulse; + g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); g_return_val_if_fail (volume != NULL, FALSE); - return pulse_connection_set_sink_volume (pulse_stream_get_connection (PULSE_STREAM (stream)), - pulse_stream_get_index (PULSE_STREAM (stream)), + pulse = PULSE_STREAM (stream); + + return pulse_connection_set_sink_volume (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), volume); } static gboolean -sink_set_active_port (MateMixerStream *stream, const gchar *port_name) +sink_set_active_port (MateMixerStream *stream, const gchar *port) { + PulseStream *pulse; + g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); - g_return_val_if_fail (port_name != NULL, FALSE); + g_return_val_if_fail (port != NULL, FALSE); + + pulse = PULSE_STREAM (stream); + + return pulse_connection_set_sink_port (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), + port); +} + +static gboolean +sink_suspend (MateMixerStream *stream) +{ + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); + + pulse = PULSE_STREAM (stream); + + return pulse_connection_suspend_sink (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), + TRUE); +} + +static gboolean +sink_resume (MateMixerStream *stream) +{ + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); + + pulse = PULSE_STREAM (stream); + + return pulse_connection_suspend_sink (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), + FALSE); +} + +static PulseMonitor * +sink_create_monitor (MateMixerStream *stream) +{ + PulseStream *pulse; + guint32 index; + + g_return_val_if_fail (PULSE_IS_SINK (stream), NULL); + + pulse = PULSE_STREAM (stream); + index = pulse_sink_get_monitor_index (pulse); + + 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)); + return NULL; + } - return pulse_connection_set_sink_port (pulse_stream_get_connection (PULSE_STREAM (stream)), - pulse_stream_get_index (PULSE_STREAM (stream)), - port_name); + return pulse_connection_create_monitor (pulse_stream_get_connection (pulse), + index, + PA_INVALID_INDEX); } diff --git a/backends/pulse/pulse-sink.h b/backends/pulse/pulse-sink.h index 22ca41f..e345d48 100644 --- a/backends/pulse/pulse-sink.h +++ b/backends/pulse/pulse-sink.h @@ -21,10 +21,9 @@ #include #include -#include - #include +#include "pulse-connection.h" #include "pulse-stream.h" G_BEGIN_DECLS @@ -38,7 +37,7 @@ G_BEGIN_DECLS #define PULSE_SINK_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_SINK, PulseSinkClass)) #define PULSE_IS_SINK_CLASS(k) \ - (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SINK)) + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SINK)) #define PULSE_SINK_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SINK, PulseSinkClass)) @@ -50,21 +49,24 @@ struct _PulseSink { PulseStream parent; + /*< private >*/ PulseSinkPrivate *priv; }; struct _PulseSinkClass { - PulseStreamClass parent; + PulseStreamClass parent_class; }; -GType pulse_sink_get_type (void) G_GNUC_CONST; +GType pulse_sink_get_type (void) G_GNUC_CONST; + +PulseStream *pulse_sink_new (PulseConnection *connection, + const pa_sink_info *info); -PulseStream *pulse_sink_new (PulseConnection *connection, - const pa_sink_info *info); +guint32 pulse_sink_get_monitor_index (PulseStream *stream); -gboolean pulse_sink_update (PulseStream *stream, - const pa_sink_info *info); +gboolean pulse_sink_update (PulseStream *stream, + const pa_sink_info *info); G_END_DECLS diff --git a/backends/pulse/pulse-source-output.c b/backends/pulse/pulse-source-output.c index 94a4963..50269ce 100644 --- a/backends/pulse/pulse-source-output.c +++ b/backends/pulse/pulse-source-output.c @@ -17,44 +17,55 @@ #include #include +#include +#include #include #include #include "pulse-connection.h" +#include "pulse-client-stream.h" +#include "pulse-monitor.h" #include "pulse-stream.h" +#include "pulse-source.h" #include "pulse-source-output.h" -struct _PulseSourceOutputPrivate -{ - guint32 index_monitor; -}; - -static gboolean source_output_set_mute (MateMixerStream *stream, gboolean mute); -static gboolean source_output_set_volume (MateMixerStream *stream, pa_cvolume *volume); +static void pulse_source_output_class_init (PulseSourceOutputClass *klass); +static void pulse_source_output_init (PulseSourceOutput *output); -G_DEFINE_TYPE (PulseSourceOutput, pulse_source_output, PULSE_TYPE_STREAM); +G_DEFINE_TYPE (PulseSourceOutput, pulse_source_output, PULSE_TYPE_CLIENT_STREAM); -static void -pulse_source_output_init (PulseSourceOutput *output) -{ - output->priv = G_TYPE_INSTANCE_GET_PRIVATE (output, - PULSE_TYPE_SOURCE_OUTPUT, - PulseSourceOutputPrivate); -} +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_class_init (PulseSourceOutputClass *klass) { - PulseStreamClass *stream_class; + PulseStreamClass *stream_class; + PulseClientStreamClass *client_class; stream_class = PULSE_STREAM_CLASS (klass); - stream_class->set_mute = source_output_set_mute; - stream_class->set_volume = source_output_set_volume; + stream_class->set_mute = source_output_set_mute; + stream_class->set_volume = source_output_set_volume; + stream_class->create_monitor = source_output_create_monitor; - g_type_class_add_private (klass, sizeof (PulseSourceOutputPrivate)); + client_class = PULSE_CLIENT_STREAM_CLASS (klass); + + client_class->set_parent = source_output_set_parent; + client_class->remove = source_output_remove; +} + +static void +pulse_source_output_init (PulseSourceOutput *output) +{ } PulseStream * @@ -62,6 +73,9 @@ pulse_source_output_new (PulseConnection *connection, const pa_source_output_inf { PulseSourceOutput *output; + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (info != NULL, NULL); + /* Consider the sink input index as unchanging parameter */ output = g_object_new (PULSE_TYPE_SOURCE_OUTPUT, "connection", connection, @@ -77,27 +91,59 @@ pulse_source_output_new (PulseConnection *connection, const pa_source_output_inf gboolean pulse_source_output_update (PulseStream *stream, const pa_source_output_info *info) { - MateMixerStreamFlags flags = MATE_MIXER_STREAM_INPUT | - MATE_MIXER_STREAM_CLIENT | - MATE_MIXER_STREAM_HAS_MUTE; + MateMixerStreamFlags flags = MATE_MIXER_STREAM_INPUT | + MATE_MIXER_STREAM_CLIENT; + gchar *name; + + const gchar *prop; + const gchar *description = NULL; g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), 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); - pulse_stream_update_channel_map (stream, &info->channel_map); + /* 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 + * this unnecessary overhead and use a custom name. + * 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); - /* Build the flag list */ - if (info->has_volume) { - flags |= MATE_MIXER_STREAM_HAS_VOLUME; - pulse_stream_update_volume (stream, &info->volume); + pulse_stream_update_name (stream, name); + g_free (name); + + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_NAME); + if (prop != NULL) + pulse_client_stream_update_app_name (MATE_MIXER_CLIENT_STREAM (stream), prop); + + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ID); + if (prop != NULL) + pulse_client_stream_update_app_id (MATE_MIXER_CLIENT_STREAM (stream), prop); + + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_VERSION); + if (prop != NULL) + pulse_client_stream_update_app_version (MATE_MIXER_CLIENT_STREAM (stream), prop); + + prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ICON_NAME); + if (prop != NULL) + pulse_client_stream_update_app_icon (MATE_MIXER_CLIENT_STREAM (stream), prop); + + prop = pa_proplist_gets (info->proplist, PA_PROP_MEDIA_ROLE); + + 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 (G_LIKELY (prop != NULL)) + description = prop; + + flags |= MATE_MIXER_STREAM_EVENT; } - if (info->volume_writable) - flags |= MATE_MIXER_STREAM_CAN_SET_VOLUME; + if (description == NULL) + description = info->name; + + pulse_stream_update_description (stream, description); if (info->client != PA_INVALID_INDEX) flags |= MATE_MIXER_STREAM_APPLICATION; @@ -107,7 +153,27 @@ pulse_source_output_update (PulseStream *stream, const pa_source_output_info *in if (pa_channel_map_can_fade (&info->channel_map)) flags |= MATE_MIXER_STREAM_CAN_FADE; +#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); + + if (info->has_volume) + pulse_stream_update_volume (stream, &info->volume, &info->channel_map); + else + pulse_stream_update_volume (stream, NULL, &info->channel_map); +#else + pulse_stream_update_flags (stream, flags); + pulse_stream_update_volume (stream, NULL, &info->channel_map); +#endif + + // XXX needs to fix monitor if parent changes g_object_thaw_notify (G_OBJECT (stream)); return TRUE; @@ -116,28 +182,80 @@ pulse_source_output_update (PulseStream *stream, const pa_source_output_info *in static gboolean source_output_set_mute (MateMixerStream *stream, gboolean mute) { - PulseStream *ps; + PulseStream *pulse; g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), FALSE); - ps = PULSE_STREAM (stream); + pulse = PULSE_STREAM (stream); - return pulse_connection_set_source_output_mute (pulse_stream_get_connection (ps), - pulse_stream_get_index (ps), + return pulse_connection_set_source_output_mute (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), mute); } static gboolean source_output_set_volume (MateMixerStream *stream, pa_cvolume *volume) { - PulseStream *ps; + PulseStream *pulse; g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), FALSE); g_return_val_if_fail (volume != NULL, FALSE); - ps = PULSE_STREAM (stream); + pulse = PULSE_STREAM (stream); - return pulse_connection_set_source_output_volume (pulse_stream_get_connection (ps), - pulse_stream_get_index (ps), + return pulse_connection_set_source_output_volume (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), volume); } + +static gboolean +source_output_set_parent (MateMixerClientStream *stream, MateMixerStream *parent) +{ + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), FALSE); + + if (G_UNLIKELY (!PULSE_IS_SOURCE (parent))) { + g_warning ("Could not change stream parent to %s: not a parent input stream", + mate_mixer_stream_get_name (parent)); + return FALSE; + } + + pulse = PULSE_STREAM (stream); + + return pulse_connection_move_sink_input (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), + pulse_stream_get_index (PULSE_STREAM (parent))); +} + +static gboolean +source_output_remove (MateMixerClientStream *stream) +{ + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), FALSE); + + pulse = PULSE_STREAM (stream); + + return pulse_connection_kill_source_output (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse)); +} + +static PulseMonitor * +source_output_create_monitor (MateMixerStream *stream) +{ + MateMixerStream *parent; + + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), NULL); + + parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (stream)); + 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)); + return NULL; + } + + return pulse_connection_create_monitor (pulse_stream_get_connection (PULSE_STREAM (stream)), + 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 554819f..7413eb1 100644 --- a/backends/pulse/pulse-source-output.h +++ b/backends/pulse/pulse-source-output.h @@ -21,10 +21,10 @@ #include #include -#include - #include +#include "pulse-client-stream.h" +#include "pulse-connection.h" #include "pulse-stream.h" G_BEGIN_DECLS @@ -38,7 +38,7 @@ G_BEGIN_DECLS #define PULSE_SOURCE_OUTPUT_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_SOURCE_OUTPUT, PulseSourceOutputClass)) #define PULSE_IS_SOURCE_OUTPUT_CLASS(k) \ - (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SOURCE_OUTPUT)) + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SOURCE_OUTPUT)) #define PULSE_SOURCE_OUTPUT_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SOURCE_OUTPUT, PulseSourceOutputClass)) @@ -48,14 +48,12 @@ typedef struct _PulseSourceOutputPrivate PulseSourceOutputPrivate; struct _PulseSourceOutput { - PulseStream parent; - - PulseSourceOutputPrivate *priv; + PulseClientStream parent; }; struct _PulseSourceOutputClass { - PulseStreamClass parent; + PulseClientStreamClass parent_class; }; GType pulse_source_output_get_type (void) G_GNUC_CONST; diff --git a/backends/pulse/pulse-source.c b/backends/pulse/pulse-source.c index 2aea6b6..e4de5fa 100644 --- a/backends/pulse/pulse-source.c +++ b/backends/pulse/pulse-source.c @@ -24,30 +24,22 @@ #include #include "pulse-connection.h" +#include "pulse-monitor.h" #include "pulse-stream.h" #include "pulse-source.h" -struct _PulseSourcePrivate -{ - guint32 index_monitored_sink; -}; - -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 void pulse_source_class_init (PulseSourceClass *klass); +static void pulse_source_init (PulseSource *source); G_DEFINE_TYPE (PulseSource, pulse_source, PULSE_TYPE_STREAM); -static void -pulse_source_init (PulseSource *source) -{ - source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source, - PULSE_TYPE_SOURCE, - PulseSourcePrivate); -} +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_class_init (PulseSourceClass *klass) @@ -59,55 +51,28 @@ pulse_source_class_init (PulseSourceClass *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; +} - g_type_class_add_private (klass, sizeof (PulseSourcePrivate)); +static void +pulse_source_init (PulseSource *source) +{ } PulseStream * pulse_source_new (PulseConnection *connection, const pa_source_info *info) { PulseSource *source; - GList *ports = NULL; - int i; - - for (i = 0; i < info->n_ports; i++) { - MateMixerPort *port; - MateMixerPortStatus status = MATE_MIXER_PORT_UNKNOWN_STATUS; - pa_source_port_info *p_info = info->ports[i]; -#if PA_CHECK_VERSION(2, 0, 0) - switch (p_info->available) { - case PA_PORT_AVAILABLE_YES: - status = MATE_MIXER_PORT_AVAILABLE; - break; - case PA_PORT_AVAILABLE_NO: - status = MATE_MIXER_PORT_UNAVAILABLE; - break; - default: - break; - } -#endif - port = mate_mixer_port_new (p_info->name, - p_info->description, - NULL, - p_info->priority, - status); - - ports = g_list_prepend (ports, port); - } + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (info != NULL, NULL); + /* Consider the sink index as unchanging parameter */ source = g_object_new (PULSE_TYPE_SOURCE, "connection", connection, "index", info->index, NULL); - /* According to the PulseAudio code, the list of sink port never changes with - * updates. - * This may be not future-proof, but checking and validating the list of ports on - * each update would be an expensive operation, so let's set the list only during - * the construction */ - pulse_stream_update_ports (PULSE_STREAM (source), g_list_reverse (ports)); - /* Other data may change at any time, so let's make a use of our update function */ pulse_source_update (PULSE_STREAM (source), info); @@ -117,42 +82,59 @@ pulse_source_new (PulseConnection *connection, const pa_source_info *info) gboolean pulse_source_update (PulseStream *stream, const pa_source_info *info) { - PulseSource *source; - MateMixerStreamFlags flags = MATE_MIXER_STREAM_INPUT | - MATE_MIXER_STREAM_HAS_MUTE | - MATE_MIXER_STREAM_HAS_VOLUME | - MATE_MIXER_STREAM_CAN_SET_VOLUME; + MateMixerStreamFlags flags = MATE_MIXER_STREAM_INPUT | + MATE_MIXER_STREAM_HAS_MUTE | + 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); - source = PULSE_SOURCE (stream); - /* 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); - pulse_stream_update_channel_map (stream, &info->channel_map); - pulse_stream_update_volume_extended (stream, - &info->volume, - info->base_volume, - info->n_volume_steps); + + /* 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); + /* Stream state */ switch (info->state) { case PA_SOURCE_RUNNING: - pulse_stream_update_status (stream, MATE_MIXER_STREAM_RUNNING); + pulse_stream_update_state (stream, MATE_MIXER_STREAM_RUNNING); break; case PA_SOURCE_IDLE: - pulse_stream_update_status (stream, MATE_MIXER_STREAM_IDLE); + pulse_stream_update_state (stream, MATE_MIXER_STREAM_IDLE); break; case PA_SOURCE_SUSPENDED: - pulse_stream_update_status (stream, MATE_MIXER_STREAM_SUSPENDED); + pulse_stream_update_state (stream, MATE_MIXER_STREAM_SUSPENDED); break; default: - pulse_stream_update_status (stream, MATE_MIXER_STREAM_UNKNOWN_STATUS); + pulse_stream_update_state (stream, MATE_MIXER_STREAM_UNKNOWN_STATE); break; } @@ -167,14 +149,14 @@ pulse_source_update (PulseStream *stream, const pa_source_info *info) 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); - if (source->priv->index_monitored_sink != info->monitor_of_sink) { - source->priv->index_monitored_sink = info->monitor_of_sink; - - // TODO: provide a property - // g_object_notify (G_OBJECT (stream), "monitor"); - } + pulse_stream_update_volume_extended (stream, + &info->volume, + &info->channel_map, + info->base_volume, + info->n_volume_steps); g_object_thaw_notify (G_OBJECT (stream)); return TRUE; @@ -183,31 +165,57 @@ pulse_source_update (PulseStream *stream, const pa_source_info *info) static gboolean source_set_mute (MateMixerStream *stream, gboolean mute) { + PulseStream *pulse; + g_return_val_if_fail (PULSE_IS_SOURCE (stream), FALSE); - return pulse_connection_set_source_mute (pulse_stream_get_connection (PULSE_STREAM (stream)), - pulse_stream_get_index (PULSE_STREAM (stream)), + pulse = PULSE_STREAM (stream); + + return pulse_connection_set_source_mute (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), mute); } static gboolean source_set_volume (MateMixerStream *stream, pa_cvolume *volume) { + PulseStream *pulse; + g_return_val_if_fail (PULSE_IS_SOURCE (stream), FALSE); g_return_val_if_fail (volume != NULL, FALSE); - return pulse_connection_set_source_volume (pulse_stream_get_connection (PULSE_STREAM (stream)), - pulse_stream_get_index (PULSE_STREAM (stream)), + pulse = PULSE_STREAM (stream); + + return pulse_connection_set_source_volume (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), volume); } static gboolean source_set_active_port (MateMixerStream *stream, const gchar *port_name) { + PulseStream *pulse; + 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 (PULSE_STREAM (stream)), - pulse_stream_get_index (PULSE_STREAM (stream)), + pulse = PULSE_STREAM (stream); + + return pulse_connection_set_source_port (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), port_name); } + +static PulseMonitor * +source_create_monitor (MateMixerStream *stream) +{ + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_SOURCE (stream), NULL); + + pulse = PULSE_STREAM (stream); + + return pulse_connection_create_monitor (pulse_stream_get_connection (pulse), + pulse_stream_get_index (pulse), + PA_INVALID_INDEX); +} diff --git a/backends/pulse/pulse-source.h b/backends/pulse/pulse-source.h index a8fd13c..499fb2c 100644 --- a/backends/pulse/pulse-source.h +++ b/backends/pulse/pulse-source.h @@ -21,10 +21,9 @@ #include #include -#include - #include +#include "pulse-connection.h" #include "pulse-stream.h" G_BEGIN_DECLS @@ -38,24 +37,21 @@ G_BEGIN_DECLS #define PULSE_SOURCE_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_SOURCE, PulseSourceClass)) #define PULSE_IS_SOURCE_CLASS(k) \ - (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SOURCE)) + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_SOURCE)) #define PULSE_SOURCE_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_SOURCE, PulseSourceClass)) typedef struct _PulseSource PulseSource; typedef struct _PulseSourceClass PulseSourceClass; -typedef struct _PulseSourcePrivate PulseSourcePrivate; struct _PulseSource { PulseStream parent; - - PulseSourcePrivate *priv; }; struct _PulseSourceClass { - PulseStreamClass parent; + PulseStreamClass parent_class; }; GType pulse_source_get_type (void) G_GNUC_CONST; diff --git a/backends/pulse/pulse-stream.c b/backends/pulse/pulse-stream.c index 529f2c9..bb50c50 100644 --- a/backends/pulse/pulse-stream.c +++ b/backends/pulse/pulse-stream.c @@ -28,6 +28,7 @@ #include "pulse-connection.h" #include "pulse-helpers.h" +#include "pulse-monitor.h" #include "pulse-stream.h" struct _PulseStreamPrivate @@ -36,10 +37,9 @@ struct _PulseStreamPrivate guint32 index_device; gchar *name; gchar *description; - gchar *icon; MateMixerDevice *device; MateMixerStreamFlags flags; - MateMixerStreamStatus status; + MateMixerStreamState state; gboolean mute; pa_cvolume volume; pa_volume_t volume_base; @@ -50,23 +50,22 @@ struct _PulseStreamPrivate GList *ports; MateMixerPort *port; PulseConnection *connection; + PulseMonitor *monitor; }; -enum -{ +enum { PROP_0, PROP_NAME, PROP_DESCRIPTION, - PROP_ICON, PROP_DEVICE, PROP_FLAGS, - PROP_STATUS, + PROP_STATE, PROP_MUTE, PROP_NUM_CHANNELS, PROP_VOLUME, - PROP_VOLUME_DB, PROP_BALANCE, PROP_FADE, + PROP_PORTS, PROP_ACTIVE_PORT, PROP_INDEX, PROP_CONNECTION, @@ -79,13 +78,16 @@ static void pulse_stream_init (PulseStream *stream) 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 const gchar * stream_get_icon (MateMixerStream *stream); static MateMixerDevice * stream_get_device (MateMixerStream *stream); static MateMixerStreamFlags stream_get_flags (MateMixerStream *stream); -static MateMixerStreamStatus stream_get_status (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); @@ -128,27 +130,34 @@ static gboolean stream_set_fade (MateMixerStream gdouble 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 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); + static gint64 stream_get_min_volume (MateMixerStream *stream); static gint64 stream_get_max_volume (MateMixerStream *stream); static gint64 stream_get_normal_volume (MateMixerStream *stream); -G_DEFINE_ABSTRACT_TYPE_WITH_CODE (PulseStream, pulse_stream, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_STREAM, - mate_mixer_stream_interface_init)) +static gboolean stream_set_cvolume (MateMixerStream *stream, + pa_cvolume *volume); static void mate_mixer_stream_interface_init (MateMixerStreamInterface *iface) { iface->get_name = stream_get_name; iface->get_description = stream_get_description; - iface->get_icon = stream_get_icon; iface->get_device = stream_get_device; iface->get_flags = stream_get_flags; - iface->get_status = stream_get_status; + 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; @@ -172,6 +181,9 @@ mate_mixer_stream_interface_init (MateMixerStreamInterface *iface) 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->list_ports = stream_list_ports; iface->get_active_port = stream_get_active_port; iface->set_active_port = stream_set_active_port; @@ -197,17 +209,14 @@ pulse_stream_get_property (GObject *object, case PROP_DESCRIPTION: g_value_set_string (value, stream->priv->description); break; - case PROP_ICON: - g_value_set_string (value, stream->priv->icon); - break; case PROP_DEVICE: g_value_set_object (value, stream->priv->device); break; case PROP_FLAGS: g_value_set_flags (value, stream->priv->flags); break; - case PROP_STATUS: - g_value_set_enum (value, stream->priv->status); + case PROP_STATE: + g_value_set_enum (value, stream->priv->state); break; case PROP_MUTE: g_value_set_boolean (value, stream->priv->mute); @@ -218,14 +227,14 @@ pulse_stream_get_property (GObject *object, case PROP_VOLUME: g_value_set_int64 (value, stream_get_volume (MATE_MIXER_STREAM (stream))); break; - case PROP_VOLUME_DB: - g_value_set_double (value, stream_get_volume_db (MATE_MIXER_STREAM (stream))); - break; case PROP_BALANCE: - g_value_set_double (value, stream->priv->balance); + g_value_set_double (value, stream_get_balance (MATE_MIXER_STREAM (stream))); break; case PROP_FADE: - g_value_set_double (value, stream->priv->fade); + g_value_set_double (value, stream_get_fade (MATE_MIXER_STREAM (stream))); + break; + case PROP_PORTS: + g_value_set_pointer (value, stream->priv->ports); break; case PROP_ACTIVE_PORT: g_value_set_object (value, stream->priv->port); @@ -253,37 +262,13 @@ pulse_stream_set_property (GObject *object, stream = PULSE_STREAM (object); switch (param_id) { - case PROP_NAME: - stream->priv->name = g_strdup (g_value_dup_string (value)); - break; - case PROP_DESCRIPTION: - stream->priv->description = g_strdup (g_value_get_string (value)); - break; - case PROP_ICON: - stream->priv->icon = g_strdup (g_value_get_string (value)); - break; - case PROP_DEVICE: - // XXX may be NULL and the device may become known after the stream, - // figure this out.. - // stream->priv->device = g_object_ref (g_value_get_object (value)); - break; - case PROP_FLAGS: - stream->priv->flags = g_value_get_flags (value); - break; - case PROP_STATUS: - stream->priv->status = g_value_get_enum (value); - break; - case PROP_MUTE: - stream->priv->mute = g_value_get_boolean (value); - break; case PROP_INDEX: stream->priv->index = g_value_get_uint (value); break; case PROP_CONNECTION: + /* Construct-only object property */ stream->priv->connection = g_value_dup_object (value); break; - case PROP_ACTIVE_PORT: - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; @@ -312,6 +297,7 @@ pulse_stream_class_init (PulseStreamClass *klass) 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", @@ -324,16 +310,15 @@ pulse_stream_class_init (PulseStreamClass *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_DEVICE, "device"); g_object_class_override_property (object_class, PROP_FLAGS, "flags"); - g_object_class_override_property (object_class, PROP_STATUS, "status"); + 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_VOLUME_DB, "volume-db"); 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)); @@ -375,7 +360,6 @@ pulse_stream_finalize (GObject *object) g_free (stream->priv->name); g_free (stream->priv->description); - g_free (stream->priv->icon); G_OBJECT_CLASS (pulse_stream_parent_class)->finalize (object); } @@ -396,6 +380,14 @@ pulse_stream_get_connection (PulseStream *stream) return stream->priv->connection; } +PulseMonitor * +pulse_stream_get_monitor (PulseStream *stream) +{ + g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); + + return stream->priv->monitor; +} + gboolean pulse_stream_update_name (PulseStream *stream, const gchar *name) { @@ -439,13 +431,13 @@ pulse_stream_update_flags (PulseStream *stream, MateMixerStreamFlags flags) } gboolean -pulse_stream_update_status (PulseStream *stream, MateMixerStreamStatus status) +pulse_stream_update_state (PulseStream *stream, MateMixerStreamState state) { g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - if (stream->priv->status != status) { - stream->priv->status = status; - g_object_notify (G_OBJECT (stream), "status"); + if (stream->priv->state != state) { + stream->priv->state = state; + g_object_notify (G_OBJECT (stream), "state"); } return TRUE; } @@ -463,63 +455,53 @@ pulse_stream_update_mute (PulseStream *stream, gboolean mute) } gboolean -pulse_stream_update_volume (PulseStream *stream, const pa_cvolume *volume) +pulse_stream_update_volume (PulseStream *stream, + const pa_cvolume *volume, + const pa_channel_map *map) { g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - if (!pa_cvolume_equal (&stream->priv->volume, volume)) { - stream->priv->volume = *volume; + /* The channel_map argument is always present, but volume is not always + * supported and might be NULL */ + if (!pa_channel_map_equal (&stream->priv->channel_map, map)) + stream->priv->channel_map = *map; + + if (volume != NULL) { + if (!pa_cvolume_equal (&stream->priv->volume, volume)) { + stream->priv->volume = *volume; - g_object_notify (G_OBJECT (stream), "volume"); + g_object_notify (G_OBJECT (stream), "volume"); - // XXX probably should notify about volume-db too but the flags may - // be known later + // XXX notify fade, balance if changed + } } return TRUE; } gboolean -pulse_stream_update_volume_extended (PulseStream *stream, - const pa_cvolume *volume, - pa_volume_t volume_base, - guint32 volume_steps) +pulse_stream_update_volume_extended (PulseStream *stream, + const pa_cvolume *volume, + const pa_channel_map *map, + pa_volume_t volume_base, + guint32 volume_steps) { g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - // XXX use volume_base and volume_steps - - if (!pa_cvolume_equal (&stream->priv->volume, volume)) { - stream->priv->volume = *volume; - - g_object_notify (G_OBJECT (stream), "volume"); - - // XXX probably should notify about volume-db too but the flags may - // be known later - } + pulse_stream_update_volume (stream, volume, map); stream->priv->volume_base = volume_base; stream->priv->volume_steps = volume_steps; return TRUE; } -gboolean -pulse_stream_update_channel_map (PulseStream *stream, const pa_channel_map *map) -{ - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - if (!pa_channel_map_equal (&stream->priv->channel_map, map)) - stream->priv->channel_map = *map; - - return TRUE; -} - gboolean pulse_stream_update_ports (PulseStream *stream, GList *ports) { g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - /* Right now we do not change the list of ports during update */ - g_warn_if_fail (stream->priv->ports == NULL); + // XXX sort them + if (stream->priv->ports) + g_list_free_full (stream->priv->ports, g_object_unref); stream->priv->ports = ports; return TRUE; @@ -555,10 +537,12 @@ pulse_stream_update_active_port (PulseStream *stream, const gchar *port_name) return TRUE; } +// XXX check these functions according to flags + static const gchar * stream_get_name (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); return PULSE_STREAM (stream)->priv->name; } @@ -566,23 +550,15 @@ stream_get_name (MateMixerStream *stream) static const gchar * stream_get_description (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); return PULSE_STREAM (stream)->priv->description; } -static const gchar * -stream_get_icon (MateMixerStream *stream) -{ - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - return PULSE_STREAM (stream)->priv->icon; -} - static MateMixerDevice * stream_get_device (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); return PULSE_STREAM (stream)->priv->device; } @@ -590,17 +566,17 @@ stream_get_device (MateMixerStream *stream) static MateMixerStreamFlags stream_get_flags (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), MATE_MIXER_STREAM_NO_FLAGS); return PULSE_STREAM (stream)->priv->flags; } -static MateMixerStreamStatus -stream_get_status (MateMixerStream *stream) +static MateMixerStreamState +stream_get_state (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), MATE_MIXER_STREAM_UNKNOWN_STATE); - return PULSE_STREAM (stream)->priv->status; + return PULSE_STREAM (stream)->priv->state; } static gboolean @@ -614,16 +590,19 @@ stream_get_mute (MateMixerStream *stream) static gboolean stream_set_mute (MateMixerStream *stream, gboolean mute) { - PulseStream *ps; + PulseStream *pulse; + gboolean ret = TRUE; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - ps = PULSE_STREAM (stream); - - if (ps->priv->mute == mute) - return TRUE; + pulse = PULSE_STREAM (stream); - return PULSE_STREAM_GET_CLASS (stream)->set_mute (stream, mute); + if (pulse->priv->mute != mute) { + ret = PULSE_STREAM_GET_CLASS (stream)->set_mute (stream, mute); + if (ret) + pulse->priv->mute = mute; + } + return ret; } static guint @@ -645,34 +624,32 @@ stream_get_volume (MateMixerStream *stream) static gboolean stream_set_volume (MateMixerStream *stream, gint64 volume) { - pa_cvolume cvolume; - PulseStream *ps; + PulseStream *pulse; + pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - ps = PULSE_STREAM (stream); - cvolume = ps->priv->volume; + if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_CAN_SET_VOLUME)) + return FALSE; + + pulse = PULSE_STREAM (stream); + cvolume = pulse->priv->volume; if (pa_cvolume_scale (&cvolume, (pa_volume_t) volume) == NULL) { - g_warning ("Invalid PulseAudio volume value %" G_GINT64_FORMAT, volume); + g_warning ("Invalid volume passed to stream %s", + mate_mixer_stream_get_name (stream)); return FALSE; } - - /* This is the only function which passes a volume request to the real class, so - * all the pa_cvolume validations are only done here */ - - - - return PULSE_STREAM_GET_CLASS (stream)->set_volume (stream, &cvolume); + return stream_set_cvolume (stream, &cvolume); } static gdouble stream_get_volume_db (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0.0); + g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) - return FALSE; + return 0; return pa_sw_volume_to_dB (stream_get_volume (stream)); } @@ -691,74 +668,72 @@ stream_set_volume_db (MateMixerStream *stream, gdouble volume_db) static MateMixerChannelPosition stream_get_channel_position (MateMixerStream *stream, guint channel) { - PulseStream *ps; + PulseStream *pulse; - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), MATE_MIXER_CHANNEL_UNKNOWN_POSITION); - ps = PULSE_STREAM (stream); + pulse = PULSE_STREAM (stream); - if (channel >= ps->priv->channel_map.channels) { - g_warning ("Invalid channel %u of stream %s", channel, ps->priv->name); + if (channel >= pulse->priv->channel_map.channels) { + g_warning ("Invalid channel %u of stream %s", channel, pulse->priv->name); return MATE_MIXER_CHANNEL_UNKNOWN_POSITION; } - return pulse_convert_position_to_pulse (ps->priv->channel_map.map[channel]); + return pulse_convert_position_to_pulse (pulse->priv->channel_map.map[channel]); } static gint64 stream_get_channel_volume (MateMixerStream *stream, guint channel) { - PulseStream *ps; + PulseStream *pulse; - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); - ps = PULSE_STREAM (stream); + pulse = PULSE_STREAM (stream); - if (channel >= ps->priv->volume.channels) { - g_warning ("Invalid channel %u of stream %s", channel, ps->priv->name); + if (channel >= pulse->priv->volume.channels) { + g_warning ("Invalid channel %u of stream %s", channel, pulse->priv->name); return stream_get_min_volume (stream); } - return (gint64) ps->priv->volume.values[channel]; + return (gint64) pulse->priv->volume.values[channel]; } static gboolean stream_set_channel_volume (MateMixerStream *stream, guint channel, gint64 volume) { - pa_cvolume cvolume; - PulseStream *pstream; + PulseStream *pulse; + pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pstream = PULSE_STREAM (stream); - cvolume = pstream->priv->volume; + pulse = PULSE_STREAM (stream); + cvolume = pulse->priv->volume; - if (channel >= pstream->priv->volume.channels) { - g_warning ("Invalid channel %u of stream %s", channel, pstream->priv->name); + if (channel >= pulse->priv->volume.channels) { + g_warning ("Invalid channel %u of stream %s", channel, pulse->priv->name); return FALSE; } - cvolume.values[channel] = (pa_volume_t) volume; - if (!pa_cvolume_valid (&cvolume)) { - g_warning ("Invalid PulseAudio volume value %" G_GINT64_FORMAT, volume); - return FALSE; - } - return PULSE_STREAM_GET_CLASS (stream)->set_volume (stream, &cvolume); + return stream_set_cvolume (stream, &cvolume); } static gdouble stream_get_channel_volume_db (MateMixerStream *stream, guint channel) { - PulseStream *pstream; + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0.0); + if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) + return 0; - pstream = PULSE_STREAM (stream); + pulse = PULSE_STREAM (stream); - if (channel >= pstream->priv->volume.channels) { - g_warning ("Invalid channel %u of stream %s", channel, pstream->priv->name); - return 0.0; + if (channel >= pulse->priv->volume.channels) { + g_warning ("Invalid channel %u of stream %s", channel, pulse->priv->name); + return 0; } - return pa_sw_volume_to_dB (pstream->priv->volume.values[channel]); + return pa_sw_volume_to_dB (pulse->priv->volume.values[channel]); } static gboolean @@ -768,6 +743,9 @@ stream_set_channel_volume_db (MateMixerStream *stream, { g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) + return FALSE; + return stream_set_channel_volume (stream, channel, pa_sw_volume_from_dB (volume_db)); @@ -776,13 +754,13 @@ stream_set_channel_volume_db (MateMixerStream *stream, static gboolean stream_has_position (MateMixerStream *stream, MateMixerChannelPosition position) { - PulseStreamPrivate *priv; + PulseStream *pulse; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - priv = PULSE_STREAM (stream)->priv; + pulse = PULSE_STREAM (stream); - return pa_channel_map_has_position (&priv->channel_map, + return pa_channel_map_has_position (&pulse->priv->channel_map, pulse_convert_position_to_pulse (position)); } @@ -790,14 +768,14 @@ static gint64 stream_get_position_volume (MateMixerStream *stream, MateMixerChannelPosition position) { - PulseStreamPrivate *priv; + PulseStream *pulse; g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); - priv = PULSE_STREAM (stream)->priv; + pulse = PULSE_STREAM (stream); - return pa_cvolume_get_position (&priv->volume, - &priv->channel_map, + return pa_cvolume_get_position (&pulse->priv->volume, + &pulse->priv->channel_map, pulse_convert_position_to_pulse (position)); } @@ -806,28 +784,32 @@ stream_set_position_volume (MateMixerStream *stream, MateMixerChannelPosition position, gint64 volume) { - PulseStreamPrivate *priv; - pa_cvolume cvolume; + PulseStream *pulse; + pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - priv = PULSE_STREAM (stream)->priv; - cvolume = priv->volume; + pulse = PULSE_STREAM (stream); + cvolume = pulse->priv->volume; if (!pa_cvolume_set_position (&cvolume, - &priv->channel_map, + &pulse->priv->channel_map, pulse_convert_position_to_pulse (position), (pa_volume_t) volume)) { + // XXX return FALSE; } - return PULSE_STREAM_GET_CLASS (stream)->set_volume (stream, &cvolume); + return stream_set_cvolume (stream, &cvolume); } static gdouble stream_get_position_volume_db (MateMixerStream *stream, MateMixerChannelPosition position) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0.0); + g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); + + if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) + return 0; return pa_sw_volume_to_dB (stream_get_position_volume (stream, position)); } @@ -839,87 +821,163 @@ stream_set_position_volume_db (MateMixerStream *stream, { g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) + return FALSE; + return stream_set_position_volume (stream, position, pa_sw_volume_from_dB (volume_db)); } static gdouble stream_get_balance (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0.0); + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); - return PULSE_STREAM (stream)->priv->balance; + pulse = PULSE_STREAM (stream); + + return pa_cvolume_get_balance (&pulse->priv->volume, + &pulse->priv->channel_map); } static gboolean stream_set_balance (MateMixerStream *stream, gdouble balance) { - PulseStream *pstream; - pa_cvolume cvolume; + PulseStream *pulse; + pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pstream = PULSE_STREAM (stream); - cvolume = pstream->priv->volume; - - if (balance == pstream->priv->balance) - return TRUE; + pulse = PULSE_STREAM (stream); + cvolume = pulse->priv->volume; if (pa_cvolume_set_balance (&cvolume, - &pstream->priv->channel_map, + &pulse->priv->channel_map, (float) balance) == NULL) { + // XXX return FALSE; } - return PULSE_STREAM_GET_CLASS (stream)->set_volume (stream, &cvolume); + return stream_set_cvolume (stream, &cvolume); } static gdouble stream_get_fade (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); + + pulse = PULSE_STREAM (stream); - return PULSE_STREAM (stream)->priv->fade; + return pa_cvolume_get_fade (&pulse->priv->volume, + &pulse->priv->channel_map); } static gboolean stream_set_fade (MateMixerStream *stream, gdouble fade) { + PulseStream *pulse; pa_cvolume cvolume; - PulseStream *pstream; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pstream = PULSE_STREAM (stream); - cvolume = pstream->priv->volume; - - if (fade == pstream->priv->fade) - return TRUE; + pulse = PULSE_STREAM (stream); + cvolume = pulse->priv->volume; if (pa_cvolume_set_fade (&cvolume, - &pstream->priv->channel_map, + &pulse->priv->channel_map, (float) fade) == NULL) { + // XXX return FALSE; } - return PULSE_STREAM_GET_CLASS (stream)->set_volume (stream, &cvolume); + return stream_set_cvolume (stream, &cvolume); } static gboolean stream_suspend (MateMixerStream *stream) { - // TODO - return TRUE; + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + + if (!(PULSE_STREAM (stream)->priv->flags & MATE_MIXER_STREAM_CAN_SUSPEND)) + return FALSE; + + return PULSE_STREAM_GET_CLASS (stream)->suspend (stream); } static gboolean stream_resume (MateMixerStream *stream) { - // TODO - return TRUE; + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + + if (!(PULSE_STREAM (stream)->priv->flags & MATE_MIXER_STREAM_CAN_SUSPEND)) + return FALSE; + + return PULSE_STREAM_GET_CLASS (stream)->resume (stream); +} + +static gboolean +stream_monitor_start (MateMixerStream *stream) +{ + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + + pulse = PULSE_STREAM (stream); + + if (!pulse->priv->monitor) { + pulse->priv->monitor = PULSE_STREAM_GET_CLASS (stream)->create_monitor (stream); + + if (G_UNLIKELY (pulse->priv->monitor == NULL)) + return FALSE; + + g_signal_connect (G_OBJECT (pulse->priv->monitor), + "value", + G_CALLBACK (stream_monitor_value), + stream); + } + return pulse_monitor_enable (pulse->priv->monitor); +} + +static void +stream_monitor_stop (MateMixerStream *stream) +{ + PulseStream *pulse; + + g_return_if_fail (PULSE_IS_STREAM (stream)); + + pulse = PULSE_STREAM (stream); + + if (pulse->priv->monitor) + pulse_monitor_disable (pulse->priv->monitor); +} + +static gboolean +stream_monitor_is_running (MateMixerStream *stream) +{ + PulseStream *pulse; + + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + + pulse = PULSE_STREAM (stream); + + if (pulse->priv->monitor) + return pulse_monitor_is_enabled (pulse->priv->monitor); + + return FALSE; +} + +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) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); return (const GList *) PULSE_STREAM (stream)->priv->ports; } @@ -927,18 +985,18 @@ stream_list_ports (MateMixerStream *stream) static MateMixerPort * stream_get_active_port (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); return PULSE_STREAM (stream)->priv->port; } static gboolean -stream_set_active_port (MateMixerStream *stream, const gchar *port_name) +stream_set_active_port (MateMixerStream *stream, const gchar *port) { g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - g_return_val_if_fail (port_name != NULL, FALSE); + g_return_val_if_fail (port != NULL, FALSE); - return PULSE_STREAM_GET_CLASS (stream)->set_active_port (stream, port_name); + return PULSE_STREAM_GET_CLASS (stream)->set_active_port (stream, port); } static gint64 @@ -958,3 +1016,22 @@ stream_get_normal_volume (MateMixerStream *stream) { return (gint64) PA_VOLUME_NORM; } + +static gboolean +stream_set_cvolume (MateMixerStream *stream, pa_cvolume *volume) +{ + PulseStream *pulse; + gboolean ret = TRUE; + + if (!pa_cvolume_valid (volume)) + return FALSE; + + pulse = PULSE_STREAM (stream); + + if (!pa_cvolume_equal (volume, &pulse->priv->volume)) { + ret = PULSE_STREAM_GET_CLASS (stream)->set_volume (stream, volume); + if (ret) + pulse->priv->volume = *volume; + } + return ret; +} diff --git a/backends/pulse/pulse-stream.h b/backends/pulse/pulse-stream.h index 49eac42..fa0b25b 100644 --- a/backends/pulse/pulse-stream.h +++ b/backends/pulse/pulse-stream.h @@ -26,6 +26,7 @@ #include #include "pulse-connection.h" +#include "pulse-monitor.h" G_BEGIN_DECLS @@ -38,7 +39,7 @@ G_BEGIN_DECLS #define PULSE_STREAM_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_STREAM, PulseStreamClass)) #define PULSE_IS_STREAM_CLASS(k) \ - (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), PULSE_TYPE_STREAM)) + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_STREAM)) #define PULSE_STREAM_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_STREAM, PulseStreamClass)) @@ -48,51 +49,63 @@ typedef struct _PulseStreamPrivate PulseStreamPrivate; struct _PulseStream { + GObject parent; + /*< private >*/ - GObject parent; - PulseStreamPrivate *priv; + PulseStreamPrivate *priv; }; struct _PulseStreamClass { - /*< private >*/ - GObjectClass parent; - - gboolean (*set_mute) (MateMixerStream *stream, - gboolean mute); - gboolean (*set_volume) (MateMixerStream *stream, - pa_cvolume *volume); - gboolean (*set_active_port) (MateMixerStream *stream, - const gchar *port_name); + GObjectClass parent_class; + + gboolean (*set_mute) (MateMixerStream *stream, + gboolean mute); + gboolean (*set_volume) (MateMixerStream *stream, + pa_cvolume *volume); + + gboolean (*set_active_port) (MateMixerStream *stream, + const gchar *port_name); + + gboolean (*suspend) (MateMixerStream *stream); + gboolean (*resume) (MateMixerStream *stream); + + PulseMonitor *(*create_monitor) (MateMixerStream *stream); }; -GType pulse_stream_get_type (void) G_GNUC_CONST; - -guint32 pulse_stream_get_index (PulseStream *stream); -PulseConnection *pulse_stream_get_connection (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_flags (PulseStream *stream, - MateMixerStreamFlags flags); -gboolean pulse_stream_update_status (PulseStream *stream, - MateMixerStreamStatus status); -gboolean pulse_stream_update_mute (PulseStream *stream, - gboolean mute); -gboolean pulse_stream_update_volume (PulseStream *stream, - const pa_cvolume *volume); -gboolean pulse_stream_update_volume_extended (PulseStream *stream, - const pa_cvolume *volume, - pa_volume_t volume_base, - guint32 volume_steps); -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 *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_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); +gboolean pulse_stream_update_volume_extended (PulseStream *stream, + const pa_cvolume *volume, + const pa_channel_map *map, + pa_volume_t volume_base, + guint32 volume_steps); + +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); G_END_DECLS -- cgit v1.2.1