diff options
Diffstat (limited to 'backends/pulse/pulse-connection.c')
-rw-r--r-- | backends/pulse/pulse-connection.c | 764 |
1 files changed, 764 insertions, 0 deletions
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); +} |