diff options
Diffstat (limited to 'backends')
-rw-r--r-- | backends/null/Makefile.am | 6 | ||||
-rw-r--r-- | backends/null/null.c | 32 | ||||
-rw-r--r-- | backends/null/null.h | 6 | ||||
-rw-r--r-- | backends/pulse/Makefile.am | 14 | ||||
-rw-r--r-- | backends/pulse/pulse-connection.c | 764 | ||||
-rw-r--r-- | backends/pulse/pulse-connection.h | 102 | ||||
-rw-r--r-- | backends/pulse/pulse-device.c | 251 | ||||
-rw-r--r-- | backends/pulse/pulse-device.h | 27 | ||||
-rw-r--r-- | backends/pulse/pulse-sink-input.c (renamed from backends/pulse/pulse-track.c) | 0 | ||||
-rw-r--r-- | backends/pulse/pulse-sink-input.h (renamed from backends/pulse/pulse-track.h) | 0 | ||||
-rw-r--r-- | backends/pulse/pulse-sink.c | 130 | ||||
-rw-r--r-- | backends/pulse/pulse-sink.h | 69 | ||||
-rw-r--r-- | backends/pulse/pulse-source-output.c | 0 | ||||
-rw-r--r-- | backends/pulse/pulse-source-output.h | 0 | ||||
-rw-r--r-- | backends/pulse/pulse-source.c | 0 | ||||
-rw-r--r-- | backends/pulse/pulse-source.h | 0 | ||||
-rw-r--r-- | backends/pulse/pulse-stream.c | 259 | ||||
-rw-r--r-- | backends/pulse/pulse-stream.h | 86 | ||||
-rw-r--r-- | backends/pulse/pulse.c | 383 | ||||
-rw-r--r-- | backends/pulse/pulse.h | 2 |
20 files changed, 1818 insertions, 313 deletions
diff --git a/backends/null/Makefile.am b/backends/null/Makefile.am index b6d92c7..f28bc06 100644 --- a/backends/null/Makefile.am +++ b/backends/null/Makefile.am @@ -6,15 +6,13 @@ AM_CPPFLAGS = \ -I$(top_srcdir) \ -DG_LOG_DOMAIN=\"libmatemixer-null\" -libmatemixer_null_la_CFLAGS = \ - $(GLIB_CFLAGS) +libmatemixer_null_la_CFLAGS = $(GLIB_CFLAGS) libmatemixer_null_la_SOURCES = \ null.c \ null.h -libmatemixer_null_la_LIBADD = \ - $(GLIB_LIBS) +libmatemixer_null_la_LIBADD = $(GLIB_LIBS) libmatemixer_null_la_LDFLAGS = \ -avoid-version \ diff --git a/backends/null/null.c b/backends/null/null.c index b96c9b0..1e8085d 100644 --- a/backends/null/null.c +++ b/backends/null/null.c @@ -26,19 +26,17 @@ #define BACKEND_NAME "Null" #define BACKEND_PRIORITY 999 -/* Support function for dynamic loading of the backend module */ -void backend_module_init (GTypeModule *module); - -const MateMixerBackendModuleInfo *backend_module_get_info (void); - static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); +static void mate_mixer_null_class_init (MateMixerNullClass *klass); +static void mate_mixer_null_class_finalize (MateMixerNullClass *klass); +static void mate_mixer_null_init (MateMixerNull *null); G_DEFINE_DYNAMIC_TYPE_EXTENDED (MateMixerNull, mate_mixer_null, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, mate_mixer_backend_interface_init)) -static MateMixerBackendModuleInfo info; +static MateMixerBackendInfo info; void backend_module_init (GTypeModule *module) @@ -48,10 +46,10 @@ backend_module_init (GTypeModule *module) info.name = BACKEND_NAME; info.priority = BACKEND_PRIORITY; info.g_type = MATE_MIXER_TYPE_NULL; - info.backend_type = MATE_MIXER_BACKEND_TYPE_NULL; + info.backend_type = MATE_MIXER_BACKEND_NULL; } -const MateMixerBackendModuleInfo * +const MateMixerBackendInfo * backend_module_get_info (void) { return &info; @@ -64,28 +62,18 @@ mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) } static void -mate_mixer_null_init (MateMixerNull *null) -{ -} - -static void -mate_mixer_null_finalize (GObject *object) +mate_mixer_null_class_init (MateMixerNullClass *klass) { - G_OBJECT_CLASS (mate_mixer_null_parent_class)->finalize (object); } +/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ static void -mate_mixer_null_class_init (MateMixerNullClass *klass) +mate_mixer_null_class_finalize (MateMixerNullClass *klass) { - GObjectClass *object_class; - - object_class = G_OBJECT_CLASS (klass); - object_class->finalize = mate_mixer_null_finalize; } -/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ static void -mate_mixer_null_class_finalize (MateMixerNullClass *klass) +mate_mixer_null_init (MateMixerNull *null) { } diff --git a/backends/null/null.h b/backends/null/null.h index d73794e..c9dd501 100644 --- a/backends/null/null.h +++ b/backends/null/null.h @@ -51,6 +51,10 @@ struct _MateMixerNullClass GType mate_mixer_null_get_type (void) G_GNUC_CONST; -gboolean mate_mixer_null_open (MateMixerBackend *backend); +/* Support function for dynamic loading of the backend module */ +void backend_module_init (GTypeModule *module); +const MateMixerBackendInfo *backend_module_get_info (void); + +gboolean mate_mixer_null_open (MateMixerBackend *backend); #endif /* MATEMIXER_NULL_H */ diff --git a/backends/pulse/Makefile.am b/backends/pulse/Makefile.am index 2f7cd6f..2cc7b5e 100644 --- a/backends/pulse/Makefile.am +++ b/backends/pulse/Makefile.am @@ -13,10 +13,20 @@ libmatemixer_pulse_la_CFLAGS = \ libmatemixer_pulse_la_SOURCES = \ pulse.c \ pulse.h \ + pulse-connection.c \ + pulse-connection.h \ pulse-device.c \ pulse-device.h \ - pulse-track.c \ - pulse-track.h + pulse-stream.c \ + pulse-stream.h \ + pulse-sink.c \ + pulse-sink.h \ + pulse-sink-input.c \ + pulse-sink-input.h \ + pulse-source.c \ + pulse-source.h \ + pulse-source-output.c \ + pulse-source-output.h libmatemixer_pulse_la_LIBADD = \ $(GLIB_LIBS) \ diff --git a/backends/pulse/pulse-connection.c b/backends/pulse/pulse-connection.c new file mode 100644 index 0000000..6c70490 --- /dev/null +++ b/backends/pulse/pulse-connection.c @@ -0,0 +1,764 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> +#include <sys/types.h> +#include <unistd.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-connection.h" + +struct _MateMixerPulseConnectionPrivate +{ + gchar *server; + gboolean reconnect; + gboolean connected; + pa_context *context; + pa_threaded_mainloop *mainloop; +}; + +enum { + PROP_0, + PROP_SERVER, + PROP_RECONNECT, + PROP_CONNECTED, + N_PROPERTIES +}; + +enum { + LIST_ITEM_CARD, + LIST_ITEM_SINK, + LIST_ITEM_SOURCE, + LIST_ITEM_SINK_INPUT, + LIST_ITEM_SOURCE_OUTPUT, + CARD_ADDED, + CARD_REMOVED, + CARD_CHANGED, + SINK_ADDED, + SINK_REMOVED, + SINK_CHANGED, + SOURCE_ADDED, + SOURCE_REMOVED, + SOURCE_CHANGED, + N_SIGNALS +}; + +static GParamSpec *properties[N_PROPERTIES] = { NULL, }; + +static guint signals[N_SIGNALS] = { 0, }; + +G_DEFINE_TYPE (MateMixerPulseConnection, mate_mixer_pulse_connection, G_TYPE_OBJECT); + +static gchar *pulse_connection_get_name (void); + +static gboolean pulse_connection_process_operation (MateMixerPulseConnection *connection, + pa_operation *o); + +static void pulse_connection_state_cb (pa_context *c, void *userdata); + +static void pulse_connection_subscribe_cb (pa_context *c, + pa_subscription_event_type_t t, + uint32_t idx, + void *userdata); + +static void pulse_connection_card_info_cb (pa_context *c, + const pa_card_info *info, + int eol, + void *userdata); + +static void pulse_connection_sink_info_cb (pa_context *c, + const pa_sink_info *info, + int eol, + void *userdata); + +static void pulse_connection_source_info_cb (pa_context *c, + const pa_source_info *info, + int eol, + void *userdata); + +static void pulse_connection_sink_input_info_cb (pa_context *c, + const pa_sink_input_info *info, + int eol, + void *userdata); + +static void pulse_connection_source_output_info_cb (pa_context *c, + const pa_source_output_info *info, + int eol, + void *userdata); + +static void +mate_mixer_pulse_connection_init (MateMixerPulseConnection *connection) +{ + connection->priv = G_TYPE_INSTANCE_GET_PRIVATE ( + connection, + MATE_MIXER_TYPE_PULSE_CONNECTION, + MateMixerPulseConnectionPrivate); +} + +static void +mate_mixer_pulse_connection_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + MateMixerPulseConnection *connection; + + connection = MATE_MIXER_PULSE_CONNECTION (object); + + switch (param_id) { + 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_CONNECTED: + g_value_set_boolean (value, connection->priv->connected); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +mate_mixer_pulse_connection_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + MateMixerPulseConnection *connection; + + connection = MATE_MIXER_PULSE_CONNECTION (object); + + switch (param_id) { + case PROP_SERVER: + connection->priv->server = g_strdup (g_value_get_string (value)); + break; + case PROP_RECONNECT: + connection->priv->reconnect = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +mate_mixer_pulse_connection_finalize (GObject *object) +{ + MateMixerPulseConnection *connection; + + connection = MATE_MIXER_PULSE_CONNECTION (object); + + g_free (connection->priv->server); + + G_OBJECT_CLASS (mate_mixer_pulse_connection_parent_class)->finalize (object); +} + +static void +mate_mixer_pulse_connection_class_init (MateMixerPulseConnectionClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = mate_mixer_pulse_connection_finalize; + object_class->get_property = mate_mixer_pulse_connection_get_property; + object_class->set_property = mate_mixer_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 to PulseAudio server is lost", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_CONNECTED] = g_param_spec_boolean ( + "connected", + "Connected", + "Connected to a PulseAudio server or not", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + signals[LIST_ITEM_CARD] = g_signal_new ( + "list-item-card", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (MateMixerPulseConnectionClass, list_item_card), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[LIST_ITEM_SINK] = g_signal_new ( + "list-item-sink", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (MateMixerPulseConnectionClass, list_item_sink), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[LIST_ITEM_SINK_INPUT] = g_signal_new ( + "list-item-sink-input", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (MateMixerPulseConnectionClass, list_item_sink_input), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[LIST_ITEM_SOURCE] = g_signal_new ( + "list-item-source", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (MateMixerPulseConnectionClass, list_item_source), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[LIST_ITEM_SOURCE_OUTPUT] = g_signal_new ( + "list-item-source-output", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (MateMixerPulseConnectionClass, list_item_source_output), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + g_type_class_add_private (object_class, sizeof (MateMixerPulseConnectionPrivate)); +} + +// XXX: pass more info about application, provide API + +MateMixerPulseConnection * +mate_mixer_pulse_connection_new (const gchar *server, const gchar *app_name) +{ + pa_threaded_mainloop *mainloop; + pa_context *context; + MateMixerPulseConnection *connection; + + mainloop = pa_threaded_mainloop_new (); + if (G_UNLIKELY (mainloop == NULL)) { + g_warning ("Failed to create PulseAudio main loop"); + return NULL; + } + + if (app_name != NULL) { + context = pa_context_new ( + pa_threaded_mainloop_get_api (mainloop), + app_name); + } else { + gchar *name = pulse_connection_get_name (); + + context = pa_context_new ( + pa_threaded_mainloop_get_api (mainloop), + name); + + g_free (name); + } + + if (G_UNLIKELY (context == NULL)) { + g_warning ("Failed to create PulseAudio context"); + + pa_threaded_mainloop_free (mainloop); + return NULL; + } + + connection = g_object_new (MATE_MIXER_TYPE_PULSE_CONNECTION, + "server", server, + "reconnect", TRUE, + NULL); + + connection->priv->mainloop = mainloop; + connection->priv->context = context; + + return connection; +} + +gboolean +mate_mixer_pulse_connection_connect (MateMixerPulseConnection *connection) +{ + int ret; + pa_operation *o; + + g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE); + + if (connection->priv->connected) + return TRUE; + + /* 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)); + return FALSE; + } + + pa_threaded_mainloop_lock (connection->priv->mainloop); + + /* Set callback for connection status changes; the callback is not really + * used when connecting the first time, it is only needed to signal + * a status change */ + pa_context_set_state_callback (connection->priv->context, + pulse_connection_state_cb, + connection); + + ret = pa_threaded_mainloop_start (connection->priv->mainloop); + if (ret < 0) { + g_warning ("Failed to start PulseAudio main loop: %s", pa_strerror (ret)); + + pa_context_disconnect (connection->priv->context); + pa_threaded_mainloop_unlock (connection->priv->mainloop); + return FALSE; + } + + while (TRUE) { + /* Wait for a connection state which tells us whether the connection + * has been established or has failed */ + pa_context_state_t state = + pa_context_get_state (connection->priv->context); + + if (state == PA_CONTEXT_READY) + break; + + if (state == PA_CONTEXT_FAILED || + state == PA_CONTEXT_TERMINATED) { + g_warning ("Failed to connect to PulseAudio server: %s", + pa_strerror (pa_context_errno (connection->priv->context))); + + pa_context_disconnect (connection->priv->context); + pa_threaded_mainloop_unlock (connection->priv->mainloop); + return FALSE; + } + pa_threaded_mainloop_wait (connection->priv->mainloop); + } + + pa_context_set_subscribe_callback (connection->priv->context, + pulse_connection_subscribe_cb, + connection); + + // XXX don't want notifications before the initial lists are downloaded + + o = pa_context_subscribe (connection->priv->context, + PA_SUBSCRIPTION_MASK_CARD | + PA_SUBSCRIPTION_MASK_SINK | + PA_SUBSCRIPTION_MASK_SOURCE | + PA_SUBSCRIPTION_MASK_SINK_INPUT | + PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, + NULL, NULL); + if (o == NULL) + g_warning ("Failed to subscribe to PulseAudio notifications: %s", + pa_strerror (pa_context_errno (connection->priv->context))); + else + pa_operation_unref (o); + + pa_threaded_mainloop_unlock (connection->priv->mainloop); + + connection->priv->connected = TRUE; + + g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_CONNECTED]); + return TRUE; +} + +void +mate_mixer_pulse_connection_disconnect (MateMixerPulseConnection *connection) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE); + + if (!connection->priv->connected) + return; + + pa_context_disconnect (connection->priv->context); + + connection->priv->connected = FALSE; + + g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_CONNECTED]); +} + +gboolean +mate_mixer_pulse_connection_get_server_info (MateMixerPulseConnection *connection) +{ + // TODO + return TRUE; +} + +gboolean +mate_mixer_pulse_connection_get_card_list (MateMixerPulseConnection *connection) +{ + pa_operation *o; + gboolean ret; + + g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE); + + pa_threaded_mainloop_lock (connection->priv->mainloop); + + o = pa_context_get_card_info_list ( + connection->priv->context, + pulse_connection_card_info_cb, + connection); + + ret = pulse_connection_process_operation (connection, o); + + pa_threaded_mainloop_unlock (connection->priv->mainloop); + return ret; +} + +gboolean +mate_mixer_pulse_connection_get_sink_list (MateMixerPulseConnection *connection) +{ + pa_operation *o; + gboolean ret; + + g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE); + + pa_threaded_mainloop_lock (connection->priv->mainloop); + + o = pa_context_get_sink_info_list ( + connection->priv->context, + pulse_connection_sink_info_cb, + connection); + + ret = pulse_connection_process_operation (connection, o); + + pa_threaded_mainloop_unlock (connection->priv->mainloop); + return ret; +} + +gboolean +mate_mixer_pulse_connection_get_sink_input_list (MateMixerPulseConnection *connection) +{ + pa_operation *o; + gboolean ret; + + g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE); + + pa_threaded_mainloop_lock (connection->priv->mainloop); + + o = pa_context_get_sink_input_info_list ( + connection->priv->context, + pulse_connection_sink_input_info_cb, + connection); + + ret = pulse_connection_process_operation (connection, o); + + pa_threaded_mainloop_unlock (connection->priv->mainloop); + return ret; +} + +gboolean +mate_mixer_pulse_connection_get_source_list (MateMixerPulseConnection *connection) +{ + pa_operation *o; + gboolean ret; + + g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE); + + pa_threaded_mainloop_lock (connection->priv->mainloop); + + o = pa_context_get_source_info_list ( + connection->priv->context, + pulse_connection_source_info_cb, + connection); + + ret = pulse_connection_process_operation (connection, o); + + pa_threaded_mainloop_unlock (connection->priv->mainloop); + return ret; +} + +gboolean +mate_mixer_pulse_connection_get_source_output_list (MateMixerPulseConnection *connection) +{ + pa_operation *o; + gboolean ret; + + g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE); + + pa_threaded_mainloop_lock (connection->priv->mainloop); + + o = pa_context_get_source_output_info_list ( + connection->priv->context, + pulse_connection_source_output_info_cb, + connection); + + ret = pulse_connection_process_operation (connection, o); + + pa_threaded_mainloop_unlock (connection->priv->mainloop); + return ret; +} + +gboolean +mate_mixer_pulse_connection_set_card_profile (MateMixerPulseConnection *connection, + const gchar *card, + const gchar *profile) +{ + pa_operation *o; + gboolean ret; + + g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE); + + pa_threaded_mainloop_lock (connection->priv->mainloop); + + o = pa_context_set_card_profile_by_name ( + connection->priv->context, + card, + profile, + NULL, NULL); + + // XXX maybe shouldn't wait for the completion + + ret = pulse_connection_process_operation (connection, o); + + pa_threaded_mainloop_unlock (connection->priv->mainloop); + return ret; +} + +gboolean +mate_mixer_pulse_connection_set_sink_mute (MateMixerPulseConnection *connection, + guint32 index, + gboolean mute) +{ + pa_operation *o; + gboolean ret; + + g_return_val_if_fail (MATE_MIXER_IS_PULSE_CONNECTION (connection), FALSE); + + pa_threaded_mainloop_lock (connection->priv->mainloop); + + o = pa_context_set_sink_mute_by_index ( + connection->priv->context, + index, + (int) mute, + NULL, NULL); + + // XXX maybe shouldn't wait for the completion + + ret = pulse_connection_process_operation (connection, o); + + pa_threaded_mainloop_unlock (connection->priv->mainloop); + return ret; +} + +static gboolean +pulse_connection_process_operation (MateMixerPulseConnection *connection, + pa_operation *o) +{ + if (o == NULL) { + g_warning ("Failed to process PulseAudio operation: %s", + pa_strerror (pa_context_errno (connection->priv->context))); + + return FALSE; + } + + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait (connection->priv->mainloop); + + pa_operation_unref (o); + return TRUE; +} + +static gchar * +pulse_connection_get_name (void) +{ + const char *name_app; + char name_buf[256]; + + /* Inspired by GStreamer's pulse plugin */ + name_app = g_get_application_name (); + if (name_app != NULL) + return g_strdup (name_app); + + if (pa_get_binary_name (name_buf, sizeof (name_buf)) != NULL) + return g_strdup (name_buf); + + return g_strdup_printf ("libmatemixer-%lu", (gulong) getpid ()); +} + +static void +pulse_connection_state_cb (pa_context *c, void *userdata) +{ + MateMixerPulseConnection *connection; + pa_context_state_t state; + + connection = MATE_MIXER_PULSE_CONNECTION (userdata); + + state = pa_context_get_state (c); + switch (state) { + case PA_CONTEXT_READY: + /* The connection is established, the context is ready to + * execute operations. */ + if (!connection->priv->connected) { + connection->priv->connected = TRUE; + + g_object_notify_by_pspec ( + G_OBJECT (connection), + properties[PROP_CONNECTED]); + } + break; + + case PA_CONTEXT_TERMINATED: + /* The connection was terminated cleanly. */ + if (connection->priv->connected) { + connection->priv->connected = FALSE; + + g_object_notify_by_pspec ( + G_OBJECT (connection), + properties[PROP_CONNECTED]); + + pa_context_disconnect (connection->priv->context); + } + break; + + case PA_CONTEXT_FAILED: + break; + + default: + break; + } + + pa_threaded_mainloop_signal (connection->priv->mainloop, 0); +} + +static void +pulse_connection_subscribe_cb (pa_context *c, + pa_subscription_event_type_t t, + uint32_t idx, + void *userdata) +{ + // TODO +} + +static void +pulse_connection_card_info_cb (pa_context *c, + const pa_card_info *info, + int eol, + void *userdata) +{ + MateMixerPulseConnection *connection; + + connection = MATE_MIXER_PULSE_CONNECTION (userdata); + + if (!eol) + g_signal_emit (G_OBJECT (connection), + signals[LIST_ITEM_CARD], + 0, + info); + + pa_threaded_mainloop_signal (connection->priv->mainloop, 0); +} + +static void +pulse_connection_sink_info_cb (pa_context *c, + const pa_sink_info *info, + int eol, + void *userdata) +{ + MateMixerPulseConnection *connection; + + connection = MATE_MIXER_PULSE_CONNECTION (userdata); + + if (!eol) + g_signal_emit (G_OBJECT (connection), + signals[LIST_ITEM_SINK], + 0, + info); + + pa_threaded_mainloop_signal (connection->priv->mainloop, 0); +} + +static void +pulse_connection_sink_input_info_cb (pa_context *c, + const pa_sink_input_info *info, + int eol, + void *userdata) +{ + MateMixerPulseConnection *connection; + + connection = MATE_MIXER_PULSE_CONNECTION (userdata); + + if (!eol) + g_signal_emit (G_OBJECT (connection), + signals[LIST_ITEM_SINK_INPUT], + 0, + info); + + pa_threaded_mainloop_signal (connection->priv->mainloop, 0); +} + +static void +pulse_connection_source_info_cb (pa_context *c, + const pa_source_info *info, + int eol, + void *userdata) +{ + MateMixerPulseConnection *connection; + + connection = MATE_MIXER_PULSE_CONNECTION (userdata); + + if (!eol) + g_signal_emit (G_OBJECT (connection), + signals[LIST_ITEM_SOURCE], + 0, + info); + + pa_threaded_mainloop_signal (connection->priv->mainloop, 0); +} + +static void +pulse_connection_source_output_info_cb (pa_context *c, + const pa_source_output_info *info, + int eol, + void *userdata) +{ + MateMixerPulseConnection *connection; + + connection = MATE_MIXER_PULSE_CONNECTION (userdata); + + if (!eol) + g_signal_emit (G_OBJECT (connection), + signals[LIST_ITEM_SOURCE_OUTPUT], + 0, + info); + + pa_threaded_mainloop_signal (connection->priv->mainloop, 0); +} diff --git a/backends/pulse/pulse-connection.h b/backends/pulse/pulse-connection.h new file mode 100644 index 0000000..85fd0b7 --- /dev/null +++ b/backends/pulse/pulse-connection.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MATEMIXER_PULSE_CONNECTION_H +#define MATEMIXER_PULSE_CONNECTION_H + +#include <glib.h> +#include <glib-object.h> + +#include <pulse/pulseaudio.h> + +G_BEGIN_DECLS + +#define MATE_MIXER_TYPE_PULSE_CONNECTION \ + (mate_mixer_pulse_connection_get_type ()) +#define MATE_MIXER_PULSE_CONNECTION(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), MATE_MIXER_TYPE_PULSE_CONNECTION, MateMixerPulseConnection)) +#define MATE_MIXER_IS_PULSE_CONNECTION(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), MATE_MIXER_TYPE_PULSE_CONNECTION)) +#define MATE_MIXER_PULSE_CONNECTION_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), MATE_MIXER_TYPE_PULSE_CONNECTION, MateMixerPulseConnectionClass)) +#define MATE_MIXER_IS_PULSE_CONNECTION_CLASS(k) \ + (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), MATE_MIXER_TYPE_PULSE_CONNECTION)) +#define MATE_MIXER_PULSE_CONNECTION_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), MATE_MIXER_TYPE_PULSE_CONNECTION, MateMixerPulseConnectionClass)) + +typedef struct _MateMixerPulseConnection MateMixerPulseConnection; +typedef struct _MateMixerPulseConnectionClass MateMixerPulseConnectionClass; +typedef struct _MateMixerPulseConnectionPrivate MateMixerPulseConnectionPrivate; + +struct _MateMixerPulseConnection +{ + GObject parent; + + MateMixerPulseConnectionPrivate *priv; +}; + +struct _MateMixerPulseConnectionClass +{ + GObjectClass parent; + + void (*disconnected) (MateMixerPulseConnection *connection); + void (*reconnected) (MateMixerPulseConnection *connection); + + void (*list_item_card) (MateMixerPulseConnection *connection, + const pa_card_info *info); + void (*list_item_sink) (MateMixerPulseConnection *connection, + const pa_sink_info *info); + void (*list_item_sink_input) (MateMixerPulseConnection *connection, + const pa_sink_input_info *info); + void (*list_item_source) (MateMixerPulseConnection *connection, + const pa_source_info *info); + void (*list_item_source_output) (MateMixerPulseConnection *connection, + const pa_source_output_info *info); +}; + +GType mate_mixer_pulse_connection_get_type (void) G_GNUC_CONST; + +MateMixerPulseConnection *mate_mixer_pulse_connection_new (const gchar *server, + const gchar *app_name); + +gboolean mate_mixer_pulse_connection_connect (MateMixerPulseConnection *connection); + +void mate_mixer_pulse_connection_disconnect (MateMixerPulseConnection *connection); + +gboolean mate_mixer_pulse_connection_get_server_info (MateMixerPulseConnection *connection); + +gboolean mate_mixer_pulse_connection_get_card_list (MateMixerPulseConnection *connection); + +gboolean mate_mixer_pulse_connection_get_sink_list (MateMixerPulseConnection *connection); + +gboolean mate_mixer_pulse_connection_get_sink_input_list (MateMixerPulseConnection *connection); + +gboolean mate_mixer_pulse_connection_get_source_list (MateMixerPulseConnection *connection); + +gboolean mate_mixer_pulse_connection_get_source_output_list (MateMixerPulseConnection *connection); + +gboolean mate_mixer_pulse_connection_set_card_profile (MateMixerPulseConnection *connection, + const gchar *device, + const gchar *profile); + +gboolean mate_mixer_pulse_connection_set_sink_mute (MateMixerPulseConnection *connection, + guint32 index, + gboolean mute); + +G_END_DECLS + +#endif /* MATEMIXER_PULSE_CONNECTION_H */ diff --git a/backends/pulse/pulse-device.c b/backends/pulse/pulse-device.c index dbc287c..a411d7f 100644 --- a/backends/pulse/pulse-device.c +++ b/backends/pulse/pulse-device.c @@ -19,30 +19,31 @@ #include <glib-object.h> #include <libmatemixer/matemixer-device.h> -#include <libmatemixer/matemixer-device-port.h> -#include <libmatemixer/matemixer-device-profile.h> +#include <libmatemixer/matemixer-port.h> +#include <libmatemixer/matemixer-profile.h> #include <pulse/pulseaudio.h> +#include "pulse-connection.h" #include "pulse-device.h" struct _MateMixerPulseDevicePrivate { - guint32 index; - GList *profiles; - GList *ports; - gchar *identifier; - gchar *name; - gchar *icon; - - MateMixerDeviceProfile *active_profile; + guint32 index; + gchar *name; + gchar *description; + GList *profiles; + GList *ports; + gchar *icon; + MateMixerProfile *profile; + MateMixerPulseConnection *connection; }; enum { PROP_0, - PROP_IDENTIFIER, PROP_NAME, + PROP_DESCRIPTION, PROP_ICON, PROP_ACTIVE_PROFILE, N_PROPERTIES @@ -57,9 +58,12 @@ G_DEFINE_TYPE_WITH_CODE (MateMixerPulseDevice, mate_mixer_pulse_device, G_TYPE_O static void mate_mixer_device_interface_init (MateMixerDeviceInterface *iface) { - iface->list_tracks = mate_mixer_pulse_device_list_tracks; - iface->get_ports = mate_mixer_pulse_device_get_ports; - iface->get_profiles = mate_mixer_pulse_device_get_profiles; + iface->get_name = mate_mixer_pulse_device_get_name; + iface->get_description = mate_mixer_pulse_device_get_description; + iface->get_icon = mate_mixer_pulse_device_get_icon; + iface->list_streams = mate_mixer_pulse_device_list_streams; + iface->list_ports = mate_mixer_pulse_device_list_ports; + iface->list_profiles = mate_mixer_pulse_device_list_profiles; iface->get_active_profile = mate_mixer_pulse_device_get_active_profile; iface->set_active_profile = mate_mixer_pulse_device_set_active_profile; } @@ -67,10 +71,9 @@ mate_mixer_device_interface_init (MateMixerDeviceInterface *iface) static void mate_mixer_pulse_device_init (MateMixerPulseDevice *device) { - device->priv = G_TYPE_INSTANCE_GET_PRIVATE ( - device, - MATE_MIXER_TYPE_PULSE_DEVICE, - MateMixerPulseDevicePrivate); + device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device, + MATE_MIXER_TYPE_PULSE_DEVICE, + MateMixerPulseDevicePrivate); } static void @@ -84,17 +87,17 @@ mate_mixer_pulse_device_get_property (GObject *object, device = MATE_MIXER_PULSE_DEVICE (object); switch (param_id) { - case PROP_IDENTIFIER: - g_value_set_string (value, device->priv->identifier); - break; case PROP_NAME: g_value_set_string (value, device->priv->name); break; + case PROP_DESCRIPTION: + g_value_set_string (value, device->priv->description); + break; case PROP_ICON: g_value_set_string (value, device->priv->icon); break; case PROP_ACTIVE_PROFILE: - g_value_set_object (value, device->priv->active_profile); + g_value_set_object (value, device->priv->profile); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); @@ -113,20 +116,15 @@ mate_mixer_pulse_device_set_property (GObject *object, device = MATE_MIXER_PULSE_DEVICE (object); switch (param_id) { - case PROP_IDENTIFIER: - device->priv->identifier = g_strdup (g_value_get_string (value)); - break; 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)); + break; case PROP_ICON: device->priv->icon = g_strdup (g_value_get_string (value)); break; - case PROP_ACTIVE_PROFILE: - mate_mixer_pulse_device_set_active_profile ( - MATE_MIXER_DEVICE (device), - g_value_get_object (value)); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; @@ -134,24 +132,38 @@ mate_mixer_pulse_device_set_property (GObject *object, } static void -mate_mixer_pulse_device_finalize (GObject *object) +mate_mixer_pulse_device_dispose (GObject *object) { MateMixerPulseDevice *device; device = MATE_MIXER_PULSE_DEVICE (object); - g_free (device->priv->identifier); - g_free (device->priv->name); - g_free (device->priv->icon); - - if (device->priv->profiles != NULL) + if (device->priv->profiles != NULL) { g_list_free_full (device->priv->profiles, g_object_unref); + device->priv->profiles = NULL; + } - if (device->priv->ports != NULL) + 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 (mate_mixer_pulse_device_parent_class)->dispose (object); +} + +static void +mate_mixer_pulse_device_finalize (GObject *object) +{ + MateMixerPulseDevice *device; - if (device->priv->active_profile != NULL) - g_object_unref (device->priv->active_profile); + device = MATE_MIXER_PULSE_DEVICE (object); + + g_free (device->priv->name); + g_free (device->priv->description); + g_free (device->priv->icon); G_OBJECT_CLASS (mate_mixer_pulse_device_parent_class)->finalize (object); } @@ -162,12 +174,13 @@ mate_mixer_pulse_device_class_init (MateMixerPulseDeviceClass *klass) GObjectClass *object_class; object_class = G_OBJECT_CLASS (klass); + object_class->dispose = mate_mixer_pulse_device_dispose; object_class->finalize = mate_mixer_pulse_device_finalize; object_class->get_property = mate_mixer_pulse_device_get_property; object_class->set_property = mate_mixer_pulse_device_set_property; - g_object_class_override_property (object_class, PROP_IDENTIFIER, "identifier"); 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"); @@ -175,19 +188,19 @@ mate_mixer_pulse_device_class_init (MateMixerPulseDeviceClass *klass) } MateMixerPulseDevice * -mate_mixer_pulse_device_new (const pa_card_info *info) +mate_mixer_pulse_device_new (MateMixerPulseConnection *connection, const pa_card_info *info) { - MateMixerPulseDevice *device; - MateMixerDeviceProfile *active_profile = NULL; - GList *profiles = NULL; - GList *ports = NULL; - guint32 i; + MateMixerPulseDevice *device; + MateMixerProfile *active_profile = NULL; + GList *profiles = NULL; + GList *ports = NULL; + guint32 i; g_return_val_if_fail (info != NULL, NULL); /* Create a list of card profiles */ for (i = 0; i < info->n_profiles; i++) { - MateMixerDeviceProfile *profile; + MateMixerProfile *profile; #if PA_CHECK_VERSION(5, 0, 0) pa_card_profile_info2 *p_info = info->profiles2[i]; @@ -199,9 +212,9 @@ mate_mixer_pulse_device_new (const pa_card_info *info) continue; #else /* The old profile list is an array of structs, not pointers */ - pa_card_profile_info *p_info = &info->profiles[i]; + pa_card_profile_info *p_info = &info->profiles[i]; #endif - profile = mate_mixer_device_profile_new ( + profile = mate_mixer_profile_new ( p_info->name, p_info->description, p_info->priority); @@ -222,28 +235,27 @@ mate_mixer_pulse_device_new (const pa_card_info *info) /* Create a list of card ports */ for (i = 0; i < info->n_ports; i++) { - MateMixerDevicePort *port; - MateMixerDevicePortDirection direction = 0; - MateMixerDevicePortStatus status = 0; - pa_card_port_info *p_info = info->ports[i]; - - if (p_info->direction & PA_DIRECTION_INPUT) - direction |= MATE_MIXER_DEVICE_PORT_DIRECTION_INPUT; - - if (p_info->direction & PA_DIRECTION_OUTPUT) - direction |= MATE_MIXER_DEVICE_PORT_DIRECTION_OUTPUT; + 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) - if (p_info->available == PA_PORT_AVAILABLE_YES) - status |= MATE_MIXER_DEVICE_PORT_STATUS_AVAILABLE; + 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_device_port_new ( - p_info->name, - p_info->description, - pa_proplist_gets (p_info->proplist, "device.icon_name"), - p_info->priority, - direction, - status); + 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); } @@ -253,15 +265,21 @@ mate_mixer_pulse_device_new (const pa_card_info *info) ports = g_list_reverse (ports); device = g_object_new (MATE_MIXER_TYPE_PULSE_DEVICE, - "identifier", info->name, - "name", pa_proplist_gets (info->proplist, "device.description"), - "icon", pa_proplist_gets (info->proplist, "device.icon_name"), - "active-profile", active_profile, - NULL); + "name", info->name, + "description", pa_proplist_gets (info->proplist, "device.description"), + "icon", pa_proplist_gets (info->proplist, "device.icon_name"), + NULL); + + 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->profiles = profiles; device->priv->ports = ports; + device->priv->connection = g_object_ref (connection); return device; } @@ -276,45 +294,104 @@ mate_mixer_pulse_device_update (MateMixerPulseDevice *device, const pa_card_info return TRUE; } +MateMixerPulseConnection * +mate_mixer_pulse_device_get_connection (MateMixerPulseDevice *device) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL); + + return device->priv->connection; +} + +guint32 +mate_mixer_pulse_device_get_index (MateMixerPulseDevice *device) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), 0); + + return device->priv->index; +} + +const gchar * +mate_mixer_pulse_device_get_name (MateMixerDevice *device) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL); + + return MATE_MIXER_PULSE_DEVICE (device)->priv->name; +} + +const gchar * +mate_mixer_pulse_device_get_description (MateMixerDevice *device) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL); + + return MATE_MIXER_PULSE_DEVICE (device)->priv->description; +} + +const gchar * +mate_mixer_pulse_device_get_icon (MateMixerDevice *device) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL); + + return MATE_MIXER_PULSE_DEVICE (device)->priv->icon; +} + const GList * -mate_mixer_pulse_device_list_tracks (MateMixerDevice *device) +mate_mixer_pulse_device_list_streams (MateMixerDevice *device) { // TODO return NULL; } const GList * -mate_mixer_pulse_device_get_ports (MateMixerDevice *device) +mate_mixer_pulse_device_list_ports (MateMixerDevice *device) { g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL); - return MATE_MIXER_PULSE_DEVICE (device)->priv->ports; + return (const GList *) MATE_MIXER_PULSE_DEVICE (device)->priv->ports; } const GList * -mate_mixer_pulse_device_get_profiles (MateMixerDevice *device) +mate_mixer_pulse_device_list_profiles (MateMixerDevice *device) { g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL); - return MATE_MIXER_PULSE_DEVICE (device)->priv->profiles; + return (const GList *) MATE_MIXER_PULSE_DEVICE (device)->priv->profiles; } -MateMixerDeviceProfile * +MateMixerProfile * mate_mixer_pulse_device_get_active_profile (MateMixerDevice *device) { g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL); - return MATE_MIXER_PULSE_DEVICE (device)->priv->active_profile; + return MATE_MIXER_PULSE_DEVICE (device)->priv->profile; } gboolean -mate_mixer_pulse_device_set_active_profile (MateMixerDevice *device, - MateMixerDeviceProfile *profile) +mate_mixer_pulse_device_set_active_profile (MateMixerDevice *device, const gchar *name) { + gboolean ret; + MateMixerPulseDevicePrivate *priv; + g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), FALSE); - g_return_val_if_fail (MATE_MIXER_IS_DEVICE_PROFILE (profile), FALSE); + g_return_val_if_fail (name != NULL, FALSE); - // TODO - // pa_context_set_card_profile_by_index () - return TRUE; + priv = MATE_MIXER_PULSE_DEVICE (device)->priv; + ret = mate_mixer_pulse_connection_set_card_profile (priv->connection, + priv->name, + name); + + // XXX decide to either confirm the change during the connection call or + // wait for a notification from Pulse +/* + if (ret) { + if (priv->profile) + g_object_unref (priv->profile); + + priv->profile = g_object_ref (profile); + + g_object_notify_by_pspec ( + G_OBJECT (device), + properties[PROP_ACTIVE_PROFILE]); + } +*/ + return ret; } diff --git a/backends/pulse/pulse-device.h b/backends/pulse/pulse-device.h index ab997fe..896b02b 100644 --- a/backends/pulse/pulse-device.h +++ b/backends/pulse/pulse-device.h @@ -22,10 +22,12 @@ #include <glib-object.h> #include <libmatemixer/matemixer-device.h> -#include <libmatemixer/matemixer-device-profile.h> +#include <libmatemixer/matemixer-profile.h> #include <pulse/pulseaudio.h> +#include "pulse-connection.h" + G_BEGIN_DECLS #define MATE_MIXER_TYPE_PULSE_DEVICE \ @@ -54,26 +56,35 @@ struct _MateMixerPulseDevice struct _MateMixerPulseDeviceClass { - GObjectClass parent; + GObjectClass parent; }; GType mate_mixer_pulse_device_get_type (void) G_GNUC_CONST; -MateMixerPulseDevice *mate_mixer_pulse_device_new (const pa_card_info *info); +MateMixerPulseDevice *mate_mixer_pulse_device_new (MateMixerPulseConnection *connection, + const pa_card_info *info); gboolean mate_mixer_pulse_device_update (MateMixerPulseDevice *device, const pa_card_info *info); +MateMixerPulseConnection *mate_mixer_pulse_device_get_connection (MateMixerPulseDevice *device); + +guint32 mate_mixer_pulse_device_get_index (MateMixerPulseDevice *device); + /* Interface implementation */ -const GList *mate_mixer_pulse_device_list_tracks (MateMixerDevice *device); +const gchar *mate_mixer_pulse_device_get_name (MateMixerDevice *device); +const gchar *mate_mixer_pulse_device_get_description (MateMixerDevice *device); +const gchar *mate_mixer_pulse_device_get_icon (MateMixerDevice *device); + +const GList *mate_mixer_pulse_device_list_streams (MateMixerDevice *device); -const GList *mate_mixer_pulse_device_get_ports (MateMixerDevice *device); -const GList *mate_mixer_pulse_device_get_profiles (MateMixerDevice *device); +const GList *mate_mixer_pulse_device_list_ports (MateMixerDevice *device); +const GList *mate_mixer_pulse_device_list_profiles (MateMixerDevice *device); -MateMixerDeviceProfile *mate_mixer_pulse_device_get_active_profile (MateMixerDevice *device); +MateMixerProfile *mate_mixer_pulse_device_get_active_profile (MateMixerDevice *device); gboolean mate_mixer_pulse_device_set_active_profile (MateMixerDevice *device, - MateMixerDeviceProfile *profile); + const gchar *name); G_END_DECLS diff --git a/backends/pulse/pulse-track.c b/backends/pulse/pulse-sink-input.c index e69de29..e69de29 100644 --- a/backends/pulse/pulse-track.c +++ b/backends/pulse/pulse-sink-input.c diff --git a/backends/pulse/pulse-track.h b/backends/pulse/pulse-sink-input.h index e69de29..e69de29 100644 --- a/backends/pulse/pulse-track.h +++ b/backends/pulse/pulse-sink-input.h diff --git a/backends/pulse/pulse-sink.c b/backends/pulse/pulse-sink.c new file mode 100644 index 0000000..64eb4c1 --- /dev/null +++ b/backends/pulse/pulse-sink.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer-stream.h> +#include <libmatemixer/matemixer-port.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-connection.h" +#include "pulse-stream.h" +#include "pulse-sink.h" + +struct _MateMixerPulseSinkPrivate +{ + guint32 index_monitor; +}; + +G_DEFINE_TYPE (MateMixerPulseSink, mate_mixer_pulse_sink, MATE_MIXER_TYPE_PULSE_STREAM); + +static void +mate_mixer_pulse_sink_init (MateMixerPulseSink *sink) +{ + sink->priv = G_TYPE_INSTANCE_GET_PRIVATE (sink, + MATE_MIXER_TYPE_PULSE_SINK, + MateMixerPulseSinkPrivate); +} + +static void +mate_mixer_pulse_sink_class_init (MateMixerPulseSinkClass *klass) +{ + MateMixerPulseStreamClass *stream_class; + + stream_class = MATE_MIXER_PULSE_STREAM_CLASS (klass); + + stream_class->set_volume = mate_mixer_pulse_sink_set_volume; + stream_class->set_mute = mate_mixer_pulse_sink_set_mute; + + g_type_class_add_private (G_OBJECT (klass), sizeof (MateMixerPulseSinkPrivate)); +} + +MateMixerPulseStream * +mate_mixer_pulse_sink_new (MateMixerPulseConnection *connection, const pa_sink_info *info) +{ + MateMixerPulseStream *stream; + GList *ports = NULL; + int i; + + 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); + } + + if (ports) + ports = g_list_reverse (ports); + + stream = g_object_new (MATE_MIXER_TYPE_PULSE_STREAM, + "connection", connection, + "index", info->index, + "name", info->name, + "description", info->description, + "channels", info->channel_map.channels, + "mute", info->mute ? TRUE : FALSE, + NULL); + + return stream; +} + +gboolean +mate_mixer_pulse_sink_set_volume (MateMixerStream *stream, guint32 volume) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE); + +/* + return mate_mixer_pulse_connection_set_sink_volume (mate_mixer_pulse_stream_get_connection (MATE_MIXER_PULSE_STREAM (stream)), + volume); +*/ + return TRUE; +} + +gboolean +mate_mixer_pulse_sink_set_mute (MateMixerStream *stream, gboolean mute) +{ + MateMixerPulseStream *pulse_stream; + + g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE); + + pulse_stream = MATE_MIXER_PULSE_STREAM (stream); + + return mate_mixer_pulse_connection_set_sink_mute (mate_mixer_pulse_stream_get_connection (pulse_stream), + mate_mixer_pulse_stream_get_index (pulse_stream), + mute); +} diff --git a/backends/pulse/pulse-sink.h b/backends/pulse/pulse-sink.h new file mode 100644 index 0000000..ef2608f --- /dev/null +++ b/backends/pulse/pulse-sink.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MATEMIXER_PULSE_SINK_H +#define MATEMIXER_PULSE_SINK_H + +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer-stream.h> + +#include <pulse/pulseaudio.h> + +G_BEGIN_DECLS + +#define MATE_MIXER_TYPE_PULSE_SINK \ + (mate_mixer_pulse_sink_get_type ()) +#define MATE_MIXER_PULSE_SINK(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), MATE_MIXER_TYPE_PULSE_SINK, MateMixerPulseSink)) +#define MATE_MIXER_IS_PULSE_SINK(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), MATE_MIXER_TYPE_PULSE_SINK)) +#define MATE_MIXER_PULSE_SINK_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), MATE_MIXER_TYPE_PULSE_SINK, MateMixerPulseSinkClass)) +#define MATE_MIXER_IS_PULSE_SINK_CLASS(k) \ + (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), MATE_MIXER_TYPE_PULSE_SINK)) +#define MATE_MIXER_PULSE_SINK_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), MATE_MIXER_TYPE_PULSE_SINK, MateMixerPulseSinkClass)) + +typedef struct _MateMixerPulseSink MateMixerPulseSink; +typedef struct _MateMixerPulseSinkClass MateMixerPulseSinkClass; +typedef struct _MateMixerPulseSinkPrivate MateMixerPulseSinkPrivate; + +struct _MateMixerPulseSink +{ + GObject parent; + + MateMixerPulseSinkPrivate *priv; +}; + +struct _MateMixerPulseSinkClass +{ + GObjectClass parent; +}; + +GType mate_mixer_pulse_sink_get_type (void) G_GNUC_CONST; + +MateMixerPulseStream *mate_mixer_pulse_sink_new (MateMixerPulseConnection *connection, + const pa_sink_info *info); + +gboolean mate_mixer_pulse_sink_set_volume (MateMixerStream *stream, guint32 volume); +gboolean mate_mixer_pulse_sink_set_mute (MateMixerStream *stream, gboolean mute); + +G_END_DECLS + +#endif /* MATEMIXER_PULSE_SINK_H */ diff --git a/backends/pulse/pulse-source-output.c b/backends/pulse/pulse-source-output.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/backends/pulse/pulse-source-output.c diff --git a/backends/pulse/pulse-source-output.h b/backends/pulse/pulse-source-output.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/backends/pulse/pulse-source-output.h diff --git a/backends/pulse/pulse-source.c b/backends/pulse/pulse-source.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/backends/pulse/pulse-source.c diff --git a/backends/pulse/pulse-source.h b/backends/pulse/pulse-source.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/backends/pulse/pulse-source.h diff --git a/backends/pulse/pulse-stream.c b/backends/pulse/pulse-stream.c new file mode 100644 index 0000000..a9e01d1 --- /dev/null +++ b/backends/pulse/pulse-stream.c @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer-stream.h> +#include <libmatemixer/matemixer-port.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-connection.h" +#include "pulse-stream.h" + +struct _MateMixerPulseStreamPrivate +{ + guint32 index; + gchar *name; + gchar *description; + gchar *icon; + guint channels; + gboolean mute; + GList *ports; + MateMixerPort *port; + MateMixerPulseConnection *connection; +}; + +enum +{ + PROP_0, + PROP_INDEX, + PROP_NAME, + PROP_DESCRIPTION, + PROP_ICON, + PROP_CHANNELS, + PROP_VOLUME, + PROP_MUTE, + N_PROPERTIES +}; + +static void mate_mixer_stream_interface_init (MateMixerStreamInterface *iface); +static void mate_mixer_pulse_stream_class_init (MateMixerPulseStreamClass *klass); +static void mate_mixer_pulse_stream_init (MateMixerPulseStream *stream); +static void mate_mixer_pulse_stream_dispose (GObject *object); +static void mate_mixer_pulse_stream_finalize (GObject *object); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (MateMixerPulseStream, mate_mixer_pulse_stream, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_STREAM, + mate_mixer_stream_interface_init)) + +static void +mate_mixer_stream_interface_init (MateMixerStreamInterface *iface) +{ + iface->get_name = mate_mixer_pulse_stream_get_name; + iface->get_description = mate_mixer_pulse_stream_get_description; + iface->get_icon = mate_mixer_pulse_stream_get_icon; + iface->list_ports = mate_mixer_pulse_stream_list_ports; +} + +static void +mate_mixer_pulse_stream_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + MateMixerPulseStream *stream; + + stream = MATE_MIXER_PULSE_STREAM (object); + + switch (param_id) { + case PROP_NAME: + g_value_set_string (value, stream->priv->name); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, stream->priv->description); + break; + case PROP_ICON: + g_value_set_string (value, stream->priv->icon); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +mate_mixer_pulse_stream_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + MateMixerPulseStream *stream; + + stream = MATE_MIXER_PULSE_STREAM (object); + + switch (param_id) { + case PROP_NAME: + stream->priv->name = g_strdup (g_value_get_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; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +mate_mixer_pulse_stream_class_init (MateMixerPulseStreamClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = mate_mixer_pulse_stream_dispose; + object_class->finalize = mate_mixer_pulse_stream_finalize; + object_class->get_property = mate_mixer_pulse_stream_get_property; + object_class->set_property = mate_mixer_pulse_stream_set_property; + + 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_type_class_add_private (object_class, sizeof (MateMixerPulseStreamPrivate)); +} + +static void +mate_mixer_pulse_stream_init (MateMixerPulseStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE ( + stream, + MATE_MIXER_TYPE_PULSE_STREAM, + MateMixerPulseStreamPrivate); +} + +static void +mate_mixer_pulse_stream_dispose (GObject *object) +{ + MateMixerPulseStream *stream; + + stream = MATE_MIXER_PULSE_STREAM (object); + + if (stream->priv->ports) { + g_list_free_full (stream->priv->ports, g_object_unref); + stream->priv->ports = NULL; + } + g_clear_object (&stream->priv->connection); + + G_OBJECT_CLASS (mate_mixer_pulse_stream_parent_class)->dispose (object); +} + +static void +mate_mixer_pulse_stream_finalize (GObject *object) +{ + MateMixerPulseStream *stream; + + stream = MATE_MIXER_PULSE_STREAM (object); + + g_free (stream->priv->name); + g_free (stream->priv->description); + g_free (stream->priv->icon); + + G_OBJECT_CLASS (mate_mixer_pulse_stream_parent_class)->finalize (object); +} + +MateMixerPulseConnection * +mate_mixer_pulse_stream_get_connection (MateMixerPulseStream *stream) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), NULL); + + return stream->priv->connection; +} + +guint32 +mate_mixer_pulse_stream_get_index (MateMixerPulseStream *stream) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE); + + return stream->priv->index; +} + +const gchar * +mate_mixer_pulse_stream_get_name (MateMixerStream *stream) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE); + + return MATE_MIXER_PULSE_STREAM (stream)->priv->name; +} + +const gchar * +mate_mixer_pulse_stream_get_description (MateMixerStream *stream) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE); + + return MATE_MIXER_PULSE_STREAM (stream)->priv->description; +} + +const gchar * +mate_mixer_pulse_stream_get_icon (MateMixerStream *stream) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE); + + return MATE_MIXER_PULSE_STREAM (stream)->priv->icon; +} + +MateMixerPort * +mate_mixer_pulse_stream_get_active_port (MateMixerStream *stream) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE); + + return MATE_MIXER_PULSE_STREAM (stream)->priv->port; +} + +const GList * +mate_mixer_pulse_stream_list_ports (MateMixerStream *stream) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE); + + return MATE_MIXER_PULSE_STREAM (stream)->priv->ports; +} + +gboolean +mate_mixer_pulse_stream_set_volume (MateMixerStream *stream, guint32 volume) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE); + + return MATE_MIXER_PULSE_STREAM_GET_CLASS (stream)->set_volume (stream, volume); +} + +gboolean +mate_mixer_pulse_stream_set_mute (MateMixerStream *stream, gboolean mute) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_STREAM (stream), FALSE); + + return MATE_MIXER_PULSE_STREAM_GET_CLASS (stream)->set_mute (stream, mute); +} + +gboolean +mate_mixer_pulse_stream_set_active_port (MateMixerStream *stream, MateMixerPort *port) +{ + return TRUE; +} diff --git a/backends/pulse/pulse-stream.h b/backends/pulse/pulse-stream.h new file mode 100644 index 0000000..3d3ee78 --- /dev/null +++ b/backends/pulse/pulse-stream.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MATEMIXER_PULSE_STREAM_H +#define MATEMIXER_PULSE_STREAM_H + +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer-stream.h> +#include <libmatemixer/matemixer-port.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-connection.h" + +G_BEGIN_DECLS + +#define MATE_MIXER_TYPE_PULSE_STREAM \ + (mate_mixer_pulse_stream_get_type ()) +#define MATE_MIXER_PULSE_STREAM(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), MATE_MIXER_TYPE_PULSE_STREAM, MateMixerPulseStream)) +#define MATE_MIXER_IS_PULSE_STREAM(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), MATE_MIXER_TYPE_PULSE_STREAM)) +#define MATE_MIXER_PULSE_STREAM_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), MATE_MIXER_TYPE_PULSE_STREAM, MateMixerPulseStreamClass)) +#define MATE_MIXER_IS_PULSE_STREAM_CLASS(k) \ + (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), MATE_MIXER_TYPE_PULSE_STREAM)) +#define MATE_MIXER_PULSE_STREAM_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), MATE_MIXER_TYPE_PULSE_STREAM, MateMixerPulseStreamClass)) + +typedef struct _MateMixerPulseStream MateMixerPulseStream; +typedef struct _MateMixerPulseStreamClass MateMixerPulseStreamClass; +typedef struct _MateMixerPulseStreamPrivate MateMixerPulseStreamPrivate; + +struct _MateMixerPulseStream +{ + GObject parent; + + MateMixerPulseStreamPrivate *priv; +}; + +struct _MateMixerPulseStreamClass +{ + GObjectClass parent; + + gboolean (*set_volume) (MateMixerStream *stream, guint32 volume); + gboolean (*set_mute) (MateMixerStream *stream, gboolean mute); +}; + +GType mate_mixer_pulse_stream_get_type (void) G_GNUC_CONST; + +MateMixerPulseConnection * mate_mixer_pulse_stream_get_connection (MateMixerPulseStream *stream); +guint32 mate_mixer_pulse_stream_get_index (MateMixerPulseStream *stream); + +/* Interface implementation */ +const gchar * mate_mixer_pulse_stream_get_name (MateMixerStream *stream); +const gchar * mate_mixer_pulse_stream_get_description (MateMixerStream *stream); +const gchar * mate_mixer_pulse_stream_get_icon (MateMixerStream *stream); + +MateMixerPort * mate_mixer_pulse_stream_get_active_port (MateMixerStream *stream); +gboolean mate_mixer_pulse_stream_set_active_port (MateMixerStream *stream, + MateMixerPort *port); +const GList * mate_mixer_pulse_stream_list_ports (MateMixerStream *stream); +gboolean mate_mixer_pulse_stream_set_mute (MateMixerStream *stream, + gboolean mute); +gboolean mate_mixer_pulse_stream_set_volume (MateMixerStream *stream, + guint32 volume); + +G_END_DECLS + +#endif /* MATEMIXER_PULSE_STREAM_H */ diff --git a/backends/pulse/pulse.c b/backends/pulse/pulse.c index d306577..59c5935 100644 --- a/backends/pulse/pulse.c +++ b/backends/pulse/pulse.c @@ -17,8 +17,6 @@ #include <glib.h> #include <glib-object.h> -#include <sys/types.h> -#include <unistd.h> #include <libmatemixer/matemixer-backend.h> #include <libmatemixer/matemixer-backend-module.h> @@ -27,47 +25,59 @@ #include <pulse/thread-mainloop.h> #include "pulse.h" +#include "pulse-connection.h" #include "pulse-device.h" +#include "pulse-stream.h" +#include "pulse-sink.h" #define BACKEND_NAME "PulseAudio" #define BACKEND_PRIORITY 0 struct _MateMixerPulsePrivate { - pa_threaded_mainloop *mainloop; - pa_context *context; - GHashTable *devices; + GHashTable *devices; + gboolean lists_loaded; + GHashTable *cards; + GHashTable *sinks; + GHashTable *sink_inputs; + GHashTable *sources; + GHashTable *source_outputs; + MateMixerPulseConnection *connection; }; /* Support function for dynamic loading of the backend module */ void backend_module_init (GTypeModule *module); -const MateMixerBackendModuleInfo *backend_module_get_info (void); +const MateMixerBackendInfo *backend_module_get_info (void); -static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); +static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); -static void pulse_card_info_cb (pa_context *c, - const pa_card_info *info, - int eol, - void *userdata); +static void pulse_card_cb (MateMixerPulseConnection *connection, + const pa_card_info *info, + MateMixerPulse *pulse); -static void pulse_card_update (MateMixerPulse *pulse, const pa_card_info *i); +static void pulse_sink_cb (MateMixerPulseConnection *connection, + const pa_sink_info *info, + MateMixerPulse *pulse); -static void pulse_state_cb (pa_context *c, void *userdata); +static void pulse_sink_input_cb (MateMixerPulseConnection *connection, + const pa_sink_input_info *info, + MateMixerPulse *pulse); -static void pulse_subscribe_cb (pa_context *c, - pa_subscription_event_type_t t, - uint32_t idx, - void *userdata); +static void pulse_source_cb (MateMixerPulseConnection *connection, + const pa_source_info *info, + MateMixerPulse *pulse); -static gchar *pulse_get_app_name (void); +static void pulse_source_output_cb (MateMixerPulseConnection *connection, + const pa_source_output_info *info, + MateMixerPulse *pulse); G_DEFINE_DYNAMIC_TYPE_EXTENDED (MateMixerPulse, mate_mixer_pulse, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, mate_mixer_backend_interface_init)) -static MateMixerBackendModuleInfo info; +static MateMixerBackendInfo info; void backend_module_init (GTypeModule *module) @@ -77,10 +87,10 @@ backend_module_init (GTypeModule *module) info.name = BACKEND_NAME; info.priority = BACKEND_PRIORITY; info.g_type = MATE_MIXER_TYPE_PULSE; - info.backend_type = MATE_MIXER_BACKEND_TYPE_PULSE; + info.backend_type = MATE_MIXER_BACKEND_PULSE; } -const MateMixerBackendModuleInfo * +const MateMixerBackendInfo * backend_module_get_info (void) { return &info; @@ -107,18 +117,70 @@ mate_mixer_pulse_init (MateMixerPulse *pulse) 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, + NULL, + g_object_unref); + pulse->priv->sink_inputs = g_hash_table_new_full ( + g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); + pulse->priv->sources = g_hash_table_new_full ( + g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); + pulse->priv->source_outputs = g_hash_table_new_full ( + g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); } static void -mate_mixer_pulse_finalize (GObject *object) +mate_mixer_pulse_dispose (GObject *object) { MateMixerPulse *pulse; pulse = MATE_MIXER_PULSE (object); - g_hash_table_destroy (pulse->priv->devices); + 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); - G_OBJECT_CLASS (mate_mixer_pulse_parent_class)->finalize (object); + G_OBJECT_CLASS (mate_mixer_pulse_parent_class)->dispose (object); } static void @@ -127,7 +189,7 @@ mate_mixer_pulse_class_init (MateMixerPulseClass *klass) GObjectClass *object_class; object_class = G_OBJECT_CLASS (klass); - object_class->finalize = mate_mixer_pulse_finalize; + object_class->dispose = mate_mixer_pulse_dispose; g_type_class_add_private (object_class, sizeof (MateMixerPulsePrivate)); } @@ -141,83 +203,48 @@ mate_mixer_pulse_class_finalize (MateMixerPulseClass *klass) gboolean mate_mixer_pulse_open (MateMixerBackend *backend) { - int ret; - gchar *app_name; - MateMixerPulse *pulse; + MateMixerPulse *pulse; + MateMixerPulseConnection *connection; - g_return_val_if_fail (MATE_MIXER_IS_BACKEND (backend), FALSE); + g_return_val_if_fail (MATE_MIXER_IS_PULSE (backend), FALSE); pulse = MATE_MIXER_PULSE (backend); - g_return_val_if_fail (pulse->priv->mainloop == NULL, FALSE); + g_return_val_if_fail (pulse->priv->connection == NULL, FALSE); - pulse->priv->mainloop = pa_threaded_mainloop_new (); - if (G_UNLIKELY (pulse->priv->mainloop == NULL)) { - g_warning ("Failed to created PulseAudio main loop"); + connection = mate_mixer_pulse_connection_new (NULL, NULL); + if (G_UNLIKELY (connection == NULL)) { + g_object_unref (connection); return FALSE; } - app_name = pulse_get_app_name (); - - pulse->priv->context = pa_context_new ( - pa_threaded_mainloop_get_api (pulse->priv->mainloop), - app_name); - - g_free (app_name); - - if (G_UNLIKELY (pulse->priv->context == NULL)) { - g_warning ("Failed to created PulseAudio context"); - - pa_threaded_mainloop_free (pulse->priv->mainloop); - pulse->priv->mainloop = NULL; - return FALSE; - } - - // XXX: investigate PA_CONTEXT_NOFAIL - ret = pa_context_connect (pulse->priv->context, NULL, PA_CONTEXT_NOFLAGS, NULL); - if (ret < 0) { - g_warning ("Failed to connect to PulseAudio server: %s", pa_strerror (ret)); - - pa_context_unref (pulse->priv->context); - pa_threaded_mainloop_free (pulse->priv->mainloop); - - pulse->priv->context = NULL; - pulse->priv->mainloop = NULL; + if (!mate_mixer_pulse_connection_connect (connection)) { + g_object_unref (connection); return FALSE; } - g_debug ("Connected to PulseAudio server"); - - pa_threaded_mainloop_lock (pulse->priv->mainloop); - - pa_context_set_state_callback (pulse->priv->context, - pulse_state_cb, - backend); - pa_context_set_subscribe_callback (pulse->priv->context, - pulse_subscribe_cb, - backend); - - ret = pa_threaded_mainloop_start (pulse->priv->mainloop); - if (ret < 0) { - g_warning ("Failed to start PulseAudio main loop: %s", pa_strerror (ret)); - - pa_threaded_mainloop_unlock (pulse->priv->mainloop); - - pa_context_unref (pulse->priv->context); - pa_threaded_mainloop_free (pulse->priv->mainloop); - - pulse->priv->context = NULL; - pulse->priv->mainloop = NULL; - return FALSE; - } - - while (pa_context_get_state (pulse->priv->context) != PA_CONTEXT_READY) { - // XXX this will get stuck if connection fails - pa_threaded_mainloop_wait (pulse->priv->mainloop); - } - - pa_threaded_mainloop_unlock (pulse->priv->mainloop); - + g_signal_connect (connection, + "list-item-card", + G_CALLBACK (pulse_card_cb), + pulse); + g_signal_connect (connection, + "list-item-sink", + G_CALLBACK (pulse_sink_cb), + pulse); + g_signal_connect (connection, + "list-item-sink-input", + G_CALLBACK (pulse_sink_input_cb), + pulse); + g_signal_connect (connection, + "list-item-source", + G_CALLBACK (pulse_source_cb), + pulse); + g_signal_connect (connection, + "list-item-source-output", + G_CALLBACK (pulse_source_output_cb), + pulse); + + pulse->priv->connection = connection; return TRUE; } @@ -226,140 +253,118 @@ mate_mixer_pulse_close (MateMixerBackend *backend) { MateMixerPulse *pulse; - g_return_if_fail (MATE_MIXER_IS_BACKEND (backend)); + g_return_if_fail (MATE_MIXER_IS_PULSE (backend)); pulse = MATE_MIXER_PULSE (backend); - g_return_if_fail (pulse->priv->mainloop != NULL); - - pa_threaded_mainloop_stop (pulse->priv->mainloop); + g_clear_object (&pulse->priv->connection); +} - pa_context_unref (pulse->priv->context); - pa_threaded_mainloop_free (pulse->priv->mainloop); +static gboolean +pulse_load_lists (MateMixerPulse *pulse) +{ + /* The Pulse server is queried for initial lists, each of the functions + * waits until the list is available and then continues with the next. + * + * One possible improvement would be to load the lists asynchronously right + * after we connect to Pulse and when the application calls one of the + * list_* () functions, check if the initial list is already available and + * eventually wait until it's available. However, this would be + * tricky with the way the Pulse API is currently used and might not + * be beneficial at all. + */ + + // XXX figure out how to handle server reconnects, ideally everything + // we know should be ditched and read again asynchronously and + // the user should only be notified about actual differences + // from the state before we were disconnected + + // mate_mixer_pulse_connection_get_server_info (pulse->priv->connection); + mate_mixer_pulse_connection_get_card_list (pulse->priv->connection); + mate_mixer_pulse_connection_get_sink_list (pulse->priv->connection); + mate_mixer_pulse_connection_get_sink_input_list (pulse->priv->connection); + mate_mixer_pulse_connection_get_source_list (pulse->priv->connection); + mate_mixer_pulse_connection_get_source_output_list (pulse->priv->connection); - pulse->priv->context = NULL; - pulse->priv->mainloop = NULL; + return TRUE; } GList * mate_mixer_pulse_list_devices (MateMixerBackend *backend) { - MateMixerPulse *pulse; - pa_operation *o; + MateMixerPulse *pulse; + GList *list; - g_return_val_if_fail (MATE_MIXER_IS_BACKEND (backend), NULL); + g_return_val_if_fail (MATE_MIXER_IS_PULSE (backend), NULL); pulse = MATE_MIXER_PULSE (backend); - pa_threaded_mainloop_lock (pulse->priv->mainloop); + if (!pulse->priv->lists_loaded) + pulse_load_lists (pulse); - o = pa_context_get_card_info_list (pulse->priv->context, pulse_card_info_cb, pulse); - if (o == NULL) { - g_warning ("Failed to read card list: %s", - pa_strerror (pa_context_errno (pulse->priv->context))); - - pa_threaded_mainloop_unlock (pulse->priv->mainloop); - return NULL; - } + list = g_hash_table_get_values (pulse->priv->devices); - while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) - pa_threaded_mainloop_wait (pulse->priv->mainloop); - - pa_operation_unref (o); - pa_threaded_mainloop_unlock (pulse->priv->mainloop); + g_list_foreach (list, (GFunc) g_object_ref, NULL); + return list; +} - return g_hash_table_get_values (pulse->priv->devices); +GList * +mate_mixer_pulse_list_streams (MateMixerBackend *backend) +{ + // TODO + return NULL; } static void -pulse_card_info_cb (pa_context *c, const pa_card_info *info, int eol, void *userdata) +pulse_card_cb (MateMixerPulseConnection *connection, + const pa_card_info *info, + MateMixerPulse *pulse) { - MateMixerPulse *pulse; - - pulse = MATE_MIXER_PULSE (userdata); - - if (!eol) - pulse_card_update (pulse, info); - - pa_threaded_mainloop_signal (pulse->priv->mainloop, 0); + MateMixerPulseDevice *device; + + device = mate_mixer_pulse_device_new (connection, info); + if (G_LIKELY (device)) + g_hash_table_insert ( + pulse->priv->devices, + GINT_TO_POINTER (mate_mixer_pulse_device_get_index (device)), + device); } static void -pulse_card_update (MateMixerPulse *pulse, const pa_card_info *info) +pulse_sink_cb (MateMixerPulseConnection *connection, + const pa_sink_info *info, + MateMixerPulse *pulse) { - gpointer item; - - item = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (info->index)); - if (item) { - /* The card is already known, just update the fields that may - * have changed */ - mate_mixer_pulse_device_update (MATE_MIXER_PULSE_DEVICE (item), info); - } else { - MateMixerPulseDevice *device = mate_mixer_pulse_device_new (info); - - if (G_UNLIKELY (device == NULL)) - g_warning ("Failed to process PulseAudio sound device"); - else - g_hash_table_insert ( - pulse->priv->devices, - GINT_TO_POINTER (info->index), - device); - } + MateMixerPulseStream *stream; + + stream = mate_mixer_pulse_sink_new (connection, info); + if (G_LIKELY (stream)) + g_hash_table_insert ( + pulse->priv->sinks, + GINT_TO_POINTER (mate_mixer_pulse_stream_get_index (stream)), + stream); } static void -pulse_state_cb (pa_context *c, void *userdata) +pulse_sink_input_cb (MateMixerPulseConnection *connection, + const pa_sink_input_info *info, + MateMixerPulse *pulse) { - MateMixerPulse *pulse; - pulse = MATE_MIXER_PULSE (userdata); - - // TODO: handle errors - - switch (pa_context_get_state (c)) { - case PA_CONTEXT_UNCONNECTED: - break; - case PA_CONTEXT_CONNECTING: - break; - case PA_CONTEXT_AUTHORIZING: - break; - case PA_CONTEXT_SETTING_NAME: - break; - case PA_CONTEXT_READY: - break; - case PA_CONTEXT_FAILED: - break; - case PA_CONTEXT_TERMINATED: - break; - default: - break; - } - - pa_threaded_mainloop_signal (pulse->priv->mainloop, FALSE); } static void -pulse_subscribe_cb (pa_context *c, - pa_subscription_event_type_t t, - uint32_t idx, - void *userdata) +pulse_source_cb (MateMixerPulseConnection *connection, + const pa_source_info *info, + MateMixerPulse *pulse) { - // TODO + } -static gchar * -pulse_get_app_name (void) +static void +pulse_source_output_cb (MateMixerPulseConnection *connection, + const pa_source_output_info *info, + MateMixerPulse *pulse) { - const char *name_app; - char name_buf[256]; - - /* Inspired by GStreamer's pulse plugin */ - name_app = g_get_application_name (); - if (name_app != NULL) - return g_strdup (name_app); - - if (pa_get_binary_name (name_buf, sizeof (name_buf)) != NULL) - return g_strdup (name_buf); - return g_strdup_printf ("libmatemixer-%lu", (gulong) getpid ()); } diff --git a/backends/pulse/pulse.h b/backends/pulse/pulse.h index 2f8414f..d94a543 100644 --- a/backends/pulse/pulse.h +++ b/backends/pulse/pulse.h @@ -54,8 +54,10 @@ struct _MateMixerPulseClass GType mate_mixer_pulse_get_type (void) G_GNUC_CONST; +/* Interface implementation */ gboolean mate_mixer_pulse_open (MateMixerBackend *backend); void mate_mixer_pulse_close (MateMixerBackend *backend); GList *mate_mixer_pulse_list_devices (MateMixerBackend *backend); +GList *mate_mixer_pulse_list_streams (MateMixerBackend *backend); #endif /* MATEMIXER_PULSE_H */ |