diff options
Diffstat (limited to 'backends/pulse/pulse-backend.c')
-rw-r--r-- | backends/pulse/pulse-backend.c | 812 |
1 files changed, 812 insertions, 0 deletions
diff --git a/backends/pulse/pulse-backend.c b/backends/pulse/pulse-backend.c new file mode 100644 index 0000000..79a69a0 --- /dev/null +++ b/backends/pulse/pulse-backend.c @@ -0,0 +1,812 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer-backend.h> +#include <libmatemixer/matemixer-backend-module.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-backend.h" +#include "pulse-connection.h" +#include "pulse-device.h" +#include "pulse-enums.h" +#include "pulse-stream.h" +#include "pulse-sink.h" +#include "pulse-sink-input.h" +#include "pulse-source.h" +#include "pulse-source-output.h" + +#define BACKEND_NAME "PulseAudio" +#define BACKEND_PRIORITY 10 + +struct _PulseBackendPrivate +{ + gchar *app_name; + gchar *app_id; + gchar *app_version; + gchar *app_icon; + gchar *server_address; + gchar *default_sink; + gchar *default_source; + GHashTable *devices; + GHashTable *cards; + GHashTable *sinks; + GHashTable *sink_inputs; + GHashTable *sources; + GHashTable *source_outputs; + MateMixerState state; + PulseConnection *connection; +}; + +enum { + PROP_0, + PROP_STATE, + N_PROPERTIES +}; + +static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (PulseBackend, pulse_backend, + G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, + mate_mixer_backend_interface_init)) + +static gboolean backend_open (MateMixerBackend *backend); +static void backend_close (MateMixerBackend *backend); + +static MateMixerState backend_get_state (MateMixerBackend *backend); + +static void backend_set_data (MateMixerBackend *backend, + const MateMixerBackendData *data); + +static GList * backend_list_devices (MateMixerBackend *backend); +static GList * backend_list_streams (MateMixerBackend *backend); + +static MateMixerStream *backend_get_default_input_stream (MateMixerBackend *backend); +static gboolean backend_set_default_input_stream (MateMixerBackend *backend, + MateMixerStream *stream); + +static MateMixerStream *backend_get_default_output_stream (MateMixerBackend *backend); +static gboolean backend_set_default_output_stream (MateMixerBackend *backend, + MateMixerStream *stream); + +static void backend_connection_state_cb (PulseConnection *connection, + GParamSpec *pspec, + PulseBackend *pulse); + +static void backend_server_info_cb (PulseConnection *connection, + const pa_server_info *info, + PulseBackend *pulse); + +static void backend_card_info_cb (PulseConnection *connection, + const pa_card_info *info, + PulseBackend *pulse); +static void backend_card_removed_cb (PulseConnection *connection, + guint index, + PulseBackend *pulse); +static void backend_sink_info_cb (PulseConnection *connection, + const pa_sink_info *info, + PulseBackend *pulse); +static void backend_sink_removed_cb (PulseConnection *connection, + guint index, + PulseBackend *pulse); +static void backend_sink_input_info_cb (PulseConnection *connection, + const pa_sink_input_info *info, + PulseBackend *pulse); +static void backend_sink_input_removed_cb (PulseConnection *connection, + guint index, + PulseBackend *pulse); +static void backend_source_info_cb (PulseConnection *connection, + const pa_source_info *info, + PulseBackend *pulse); +static void backend_source_removed_cb (PulseConnection *connection, + guint index, + PulseBackend *pulse); +static void backend_source_output_info_cb (PulseConnection *connection, + const pa_source_output_info *info, + PulseBackend *pulse); +static void backend_source_output_removed_cb (PulseConnection *connection, + guint index, + PulseBackend *pulse); + +static gint backend_compare_devices (gconstpointer a, + gconstpointer b); +static gint backend_compare_streams (gconstpointer a, + gconstpointer b); + +static MateMixerBackendInfo info; + +void +backend_module_init (GTypeModule *module) +{ + pulse_backend_register_type (module); + + info.name = BACKEND_NAME; + info.priority = BACKEND_PRIORITY; + info.g_type = PULSE_TYPE_BACKEND; + info.backend_type = MATE_MIXER_BACKEND_PULSE; +} + +const MateMixerBackendInfo * +backend_module_get_info (void) +{ + return &info; +} + +static void +mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) +{ + iface->open = backend_open; + iface->close = backend_close; + iface->get_state = backend_get_state; + iface->set_data = backend_set_data; + iface->list_devices = backend_list_devices; + iface->list_streams = backend_list_streams; + iface->get_default_input_stream = backend_get_default_input_stream; + iface->set_default_input_stream = backend_set_default_input_stream; + iface->get_default_output_stream = backend_get_default_output_stream; + iface->set_default_output_stream = backend_set_default_output_stream; +} + +static void +pulse_backend_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + PulseBackend *pulse; + + pulse = PULSE_BACKEND (object); + + switch (param_id) { + case PROP_STATE: + g_value_set_enum (value, pulse->priv->state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +pulse_backend_init (PulseBackend *pulse) +{ + pulse->priv = G_TYPE_INSTANCE_GET_PRIVATE (pulse, + PULSE_TYPE_BACKEND, + PulseBackendPrivate); + pulse->priv->devices = + g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); + pulse->priv->cards = + g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); + pulse->priv->sinks = + g_hash_table_new_full (g_direct_hash, + g_direct_equal, + 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 +pulse_backend_dispose (GObject *object) +{ + PulseBackend *pulse; + + pulse = PULSE_BACKEND (object); + + if (pulse->priv->devices) { + g_hash_table_destroy (pulse->priv->devices); + pulse->priv->devices = NULL; + } + + if (pulse->priv->cards) { + g_hash_table_destroy (pulse->priv->cards); + pulse->priv->cards = NULL; + } + + if (pulse->priv->sinks) { + g_hash_table_destroy (pulse->priv->sinks); + pulse->priv->devices = NULL; + } + + if (pulse->priv->sink_inputs) { + g_hash_table_destroy (pulse->priv->sink_inputs); + pulse->priv->devices = NULL; + } + + if (pulse->priv->sources) { + g_hash_table_destroy (pulse->priv->sources); + pulse->priv->devices = NULL; + } + + if (pulse->priv->source_outputs) { + g_hash_table_destroy (pulse->priv->source_outputs); + pulse->priv->source_outputs = NULL; + } + + g_clear_object (&pulse->priv->connection); + + G_OBJECT_CLASS (pulse_backend_parent_class)->dispose (object); +} + +static void +pulse_backend_finalize (GObject *object) +{ + PulseBackend *pulse; + + pulse = PULSE_BACKEND (object); + + g_free (pulse->priv->app_name); + g_free (pulse->priv->app_id); + g_free (pulse->priv->app_version); + g_free (pulse->priv->app_icon); + g_free (pulse->priv->server_address); + + G_OBJECT_CLASS (pulse_backend_parent_class)->finalize (object); +} + +static void +pulse_backend_class_init (PulseBackendClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = pulse_backend_dispose; + object_class->finalize = pulse_backend_finalize; + object_class->get_property = pulse_backend_get_property; + + g_object_class_override_property (object_class, PROP_STATE, "state"); + + g_type_class_add_private (klass, sizeof (PulseBackendPrivate)); +} + +/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ +static void +pulse_backend_class_finalize (PulseBackendClass *klass) +{ +} + +static gboolean +backend_open (MateMixerBackend *backend) +{ + PulseBackend *pulse; + PulseConnection *connection; + + g_return_val_if_fail (PULSE_IS_BACKEND (backend), FALSE); + + pulse = PULSE_BACKEND (backend); + + if (G_UNLIKELY (pulse->priv->connection != NULL)) + return TRUE; + + connection = pulse_connection_new (pulse->priv->app_name, + pulse->priv->app_id, + pulse->priv->app_version, + pulse->priv->app_icon, + pulse->priv->server_address); + if (G_UNLIKELY (connection == NULL)) { + pulse->priv->state = MATE_MIXER_STATE_FAILED; + return FALSE; + } + + g_signal_connect (connection, + "notify::state", + G_CALLBACK (backend_connection_state_cb), + pulse); + g_signal_connect (connection, + "server-info", + G_CALLBACK (backend_server_info_cb), + pulse); + g_signal_connect (connection, + "card-info", + G_CALLBACK (backend_card_info_cb), + pulse); + g_signal_connect (connection, + "card-removed", + G_CALLBACK (backend_card_removed_cb), + pulse); + g_signal_connect (connection, + "sink-info", + G_CALLBACK (backend_sink_info_cb), + pulse); + g_signal_connect (connection, + "sink-removed", + G_CALLBACK (backend_sink_removed_cb), + pulse); + g_signal_connect (connection, + "sink-input-info", + G_CALLBACK (backend_sink_input_info_cb), + pulse); + g_signal_connect (connection, + "sink-input-removed", + G_CALLBACK (backend_sink_input_removed_cb), + pulse); + g_signal_connect (connection, + "source-info", + G_CALLBACK (backend_source_info_cb), + pulse); + g_signal_connect (connection, + "source-removed", + G_CALLBACK (backend_source_removed_cb), + pulse); + g_signal_connect (connection, + "source-output-info", + G_CALLBACK (backend_source_output_info_cb), + pulse); + g_signal_connect (connection, + "source-output-removed", + G_CALLBACK (backend_source_output_removed_cb), + pulse); + + if (!pulse_connection_connect (connection)) { + pulse->priv->state = MATE_MIXER_STATE_FAILED; + g_object_unref (connection); + return FALSE; + } + pulse->priv->connection = connection; + pulse->priv->state = MATE_MIXER_STATE_CONNECTING; + return TRUE; +} + +static void +backend_close (MateMixerBackend *backend) +{ + g_return_if_fail (PULSE_IS_BACKEND (backend)); + + g_clear_object (&PULSE_BACKEND (backend)->priv->connection); +} + +static MateMixerState +backend_get_state (MateMixerBackend *backend) +{ + g_return_val_if_fail (PULSE_IS_BACKEND (backend), MATE_MIXER_STATE_UNKNOWN); + + return PULSE_BACKEND (backend)->priv->state; +} + +static void +backend_set_data (MateMixerBackend *backend, const MateMixerBackendData *data) +{ + PulseBackend *pulse; + + if (data == NULL) + return; + + g_return_if_fail (PULSE_IS_BACKEND (backend)); + + pulse = PULSE_BACKEND (backend); + + g_free (data->app_name); + g_free (data->app_id); + g_free (data->app_version); + g_free (data->app_icon); + g_free (data->server_address); + + pulse->priv->app_name = g_strdup (data->app_name); + pulse->priv->app_id = g_strdup (data->app_id); + pulse->priv->app_version = g_strdup (data->app_version); + pulse->priv->app_icon = g_strdup (data->app_icon); + pulse->priv->server_address = g_strdup (data->server_address); +} + +static GList * +backend_list_devices (MateMixerBackend *backend) +{ + GList *list; + + g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + + list = g_hash_table_get_values (PULSE_BACKEND (backend)->priv->devices); + + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return g_list_sort (list, backend_compare_devices); +} + +static GList * +backend_list_streams (MateMixerBackend *backend) +{ + GList *list; + PulseBackend *pulse; + + g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + + pulse = PULSE_BACKEND (backend); + + list = g_list_concat (g_hash_table_get_values (pulse->priv->sinks), + g_hash_table_get_values (pulse->priv->sink_inputs)); + list = g_list_concat (list, + g_hash_table_get_values (pulse->priv->sources)); + list = g_list_concat (list, + g_hash_table_get_values (pulse->priv->source_outputs)); + + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return g_list_sort (list, backend_compare_streams); +} + +static MateMixerStream * +backend_get_default_input_stream (MateMixerBackend *backend) +{ + return NULL; +} + +static gboolean +backend_set_default_input_stream (MateMixerBackend *backend, MateMixerStream *stream) +{ + PulseBackend *pulse; + + g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); + + pulse = PULSE_BACKEND (backend); + + return pulse_connection_set_default_source (pulse->priv->connection, + mate_mixer_stream_get_name (stream)); +} + +static MateMixerStream * +backend_get_default_output_stream (MateMixerBackend *backend) +{ + return NULL; +} + +static gboolean +backend_set_default_output_stream (MateMixerBackend *backend, MateMixerStream *stream) +{ + PulseBackend *pulse; + + g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); + + pulse = PULSE_BACKEND (backend); + + return pulse_connection_set_default_sink (pulse->priv->connection, + mate_mixer_stream_get_name (stream)); +} + +static void +backend_connection_state_cb (PulseConnection *connection, + GParamSpec *pspec, + PulseBackend *pulse) +{ + PulseConnectionState state = pulse_connection_get_state (connection); + + switch (state) { + case PULSE_CONNECTION_DISCONNECTED: + break; + case PULSE_CONNECTION_CONNECTING: + break; + case PULSE_CONNECTION_AUTHORIZING: + break; + case PULSE_CONNECTION_LOADING: + break; + case PULSE_CONNECTION_CONNECTED: + pulse->priv->state = MATE_MIXER_STATE_READY; + + g_object_notify (G_OBJECT (pulse), "state"); + break; + } +} + +static void +backend_server_info_cb (PulseConnection *connection, + const pa_server_info *info, + PulseBackend *pulse) +{ + // XXX add property + + if (g_strcmp0 (pulse->priv->default_sink, info->default_sink_name)) { + g_free (pulse->priv->default_sink); + + pulse->priv->default_sink = g_strdup (info->default_sink_name); + // g_object_notify (G_OBJECT (pulse), "default-output"); + } + + if (g_strcmp0 (pulse->priv->default_source, info->default_source_name)) { + g_free (pulse->priv->default_source); + + pulse->priv->default_source = g_strdup (info->default_source_name); + // g_object_notify (G_OBJECT (pulse), "default-input"); + } +} + +static void +backend_card_info_cb (PulseConnection *connection, + const pa_card_info *info, + PulseBackend *pulse) +{ + gpointer p; + + p = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (info->index)); + if (!p) { + PulseDevice *device; + + device = pulse_device_new (connection, info); + g_hash_table_insert (pulse->priv->devices, + GINT_TO_POINTER (pulse_device_get_index (device)), + device); + g_signal_emit_by_name (G_OBJECT (pulse), + "device-added", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + } else { + pulse_device_update (PULSE_DEVICE (p), info); + + g_signal_emit_by_name (G_OBJECT (pulse), + "device-changed", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (p))); + } +} + +static void +backend_card_removed_cb (PulseConnection *connection, + guint index, + PulseBackend *pulse) +{ + gpointer p; + gchar *name; + + p = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (index)); + if (G_UNLIKELY (p == NULL)) + return; + + name = g_strdup (mate_mixer_device_get_name (MATE_MIXER_DEVICE (p))); + + g_hash_table_remove (pulse->priv->devices, GINT_TO_POINTER (index)); + if (G_LIKELY (name != NULL)) + g_signal_emit_by_name (G_OBJECT (pulse), + "device-removed", + name); + g_free (name); +} + +static void +backend_sink_info_cb (PulseConnection *connection, + const pa_sink_info *info, + PulseBackend *pulse) +{ + gpointer p; + + p = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (info->index)); + if (!p) { + PulseStream *stream; + + stream = pulse_sink_new (connection, info); + g_hash_table_insert (pulse->priv->sinks, + GINT_TO_POINTER (pulse_stream_get_index (stream)), + stream); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-added", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + } else { + pulse_sink_update (p, info); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-changed", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (p))); + } +} + +static void +backend_sink_removed_cb (PulseConnection *connection, + guint index, + PulseBackend *pulse) +{ + gpointer p; + gchar *name; + + p = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (index)); + if (G_UNLIKELY (p == NULL)) + return; + + name = g_strdup (mate_mixer_stream_get_name (MATE_MIXER_STREAM (p))); + + g_hash_table_remove (pulse->priv->sinks, GINT_TO_POINTER (index)); + if (G_LIKELY (name != NULL)) + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-removed", + name); + g_free (name); +} + +static void +backend_sink_input_info_cb (PulseConnection *connection, + const pa_sink_input_info *info, + PulseBackend *pulse) +{ + gpointer p; + + p = g_hash_table_lookup (pulse->priv->sink_inputs, GINT_TO_POINTER (info->index)); + if (!p) { + PulseStream *stream; + + stream = pulse_sink_input_new (connection, info); + g_hash_table_insert (pulse->priv->sink_inputs, + GINT_TO_POINTER (pulse_stream_get_index (stream)), + stream); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-added", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + } else { + pulse_sink_input_update (p, info); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-changed", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (p))); + } +} + +static void +backend_sink_input_removed_cb (PulseConnection *connection, + guint index, + PulseBackend *pulse) +{ + gpointer p; + gchar *name; + + p = g_hash_table_lookup (pulse->priv->sink_inputs, GINT_TO_POINTER (index)); + if (G_UNLIKELY (p == NULL)) + return; + + name = g_strdup (mate_mixer_stream_get_name (MATE_MIXER_STREAM (p))); + + g_hash_table_remove (pulse->priv->sink_inputs, GINT_TO_POINTER (index)); + if (G_LIKELY (name != NULL)) + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-removed", + name); + g_free (name); +} + +static void +backend_source_info_cb (PulseConnection *connection, + const pa_source_info *info, + PulseBackend *pulse) +{ + gpointer p; + + p = g_hash_table_lookup (pulse->priv->sources, GINT_TO_POINTER (info->index)); + if (!p) { + PulseStream *stream; + + stream = pulse_source_new (connection, info); + g_hash_table_insert (pulse->priv->sources, + GINT_TO_POINTER (pulse_stream_get_index (stream)), + stream); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-added", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + } else { + pulse_source_update (p, info); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-changed", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (p))); + } +} + +static void +backend_source_removed_cb (PulseConnection *connection, + guint index, + PulseBackend *pulse) +{ + gpointer p; + gchar *name; + + p = g_hash_table_lookup (pulse->priv->sources, GINT_TO_POINTER (index)); + if (G_UNLIKELY (p == NULL)) + return; + + name = g_strdup (mate_mixer_stream_get_name (MATE_MIXER_STREAM (p))); + + g_hash_table_remove (pulse->priv->sources, GINT_TO_POINTER (index)); + if (G_LIKELY (name != NULL)) + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-removed", + name); + g_free (name); +} + +static void +backend_source_output_info_cb (PulseConnection *connection, + const pa_source_output_info *info, + PulseBackend *pulse) +{ + gpointer p; + + p = g_hash_table_lookup (pulse->priv->source_outputs, GINT_TO_POINTER (info->index)); + if (!p) { + PulseStream *stream; + + stream = pulse_source_output_new (connection, info); + g_hash_table_insert (pulse->priv->source_outputs, + GINT_TO_POINTER (pulse_stream_get_index (stream)), + stream); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-added", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + } else { + pulse_source_output_update (p, info); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-changed", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (p))); + } +} + +static void +backend_source_output_removed_cb (PulseConnection *connection, + guint index, + PulseBackend *pulse) +{ + gpointer p; + gchar *name; + + p = g_hash_table_lookup (pulse->priv->source_outputs, GINT_TO_POINTER (index)); + if (G_UNLIKELY (p == NULL)) + return; + + name = g_strdup (mate_mixer_stream_get_name (MATE_MIXER_STREAM (p))); + + g_hash_table_remove (pulse->priv->source_outputs, GINT_TO_POINTER (index)); + if (G_LIKELY (name != NULL)) + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-removed", + name); + g_free (name); +} + +static gint +backend_compare_devices (gconstpointer a, gconstpointer b) +{ + return strcmp (mate_mixer_device_get_name (MATE_MIXER_DEVICE (a)), + mate_mixer_device_get_name (MATE_MIXER_DEVICE (b))); +} + +static gint +backend_compare_streams (gconstpointer a, gconstpointer b) +{ + return strcmp (mate_mixer_stream_get_name (MATE_MIXER_STREAM (a)), + mate_mixer_stream_get_name (MATE_MIXER_STREAM (b))); +} |