diff options
Diffstat (limited to 'backends/pulse/pulse.c')
-rw-r--r-- | backends/pulse/pulse.c | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/backends/pulse/pulse.c b/backends/pulse/pulse.c new file mode 100644 index 0000000..e969dcf --- /dev/null +++ b/backends/pulse/pulse.c @@ -0,0 +1,371 @@ +/* + * 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 <libmatemixer/matemixer-backend.h> +#include <libmatemixer/matemixer-backend-module.h> + +#include <pulse/pulseaudio.h> +#include <pulse/thread-mainloop.h> + +#include "pulse.h" +#include "pulse-device.h" + +#define BACKEND_NAME "PulseAudio" +#define BACKEND_PRIORITY 0 + +struct _MateMixerPulsePrivate +{ + pa_threaded_mainloop *mainloop; + pa_context *context; + GHashTable *devices; +}; + +/* Support function for dynamic loading of the backend module */ +void backend_module_init (GTypeModule *module); +void backend_module_free (void); + +const MateMixerBackendModuleInfo *backend_module_get_info (void); + +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_update (MateMixerPulse *pulse, const pa_card_info *i); + +static void pulse_state_cb (pa_context *c, void *userdata); + +static void pulse_subscribe_cb (pa_context *c, + pa_subscription_event_type_t t, + uint32_t idx, + void *userdata); + +static gchar *pulse_get_app_name (void); + +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; + +void +backend_module_init (GTypeModule *module) +{ + mate_mixer_pulse_register_type (module); + + info.name = BACKEND_NAME; + info.priority = BACKEND_PRIORITY; + info.g_type = MATE_MIXER_TYPE_PULSE; + info.backend_type = MATE_MIXER_BACKEND_TYPE_PULSE; +} + +void +backend_module_free (void) +{ +} + +const MateMixerBackendModuleInfo * +backend_module_get_info (void) +{ + return &info; +} + +static void +mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) +{ + iface->open = mate_mixer_pulse_open; + iface->close = mate_mixer_pulse_close; + iface->list_devices = mate_mixer_pulse_list_devices; +} + +static void +mate_mixer_pulse_init (MateMixerPulse *pulse) +{ + pulse->priv = G_TYPE_INSTANCE_GET_PRIVATE ( + pulse, + MATE_MIXER_TYPE_PULSE, + MateMixerPulsePrivate); + + pulse->priv->devices = g_hash_table_new_full ( + g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); +} + +static void +mate_mixer_pulse_finalize (GObject *object) +{ + MateMixerPulse *pulse; + + pulse = MATE_MIXER_PULSE (object); + + g_hash_table_destroy (pulse->priv->devices); + + G_OBJECT_CLASS (mate_mixer_pulse_parent_class)->finalize (object); +} + +static void +mate_mixer_pulse_class_init (MateMixerPulseClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = mate_mixer_pulse_finalize; + + g_type_class_add_private (object_class, sizeof (MateMixerPulsePrivate)); +} + +/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ +static void +mate_mixer_pulse_class_finalize (MateMixerPulseClass *klass) +{ +} + +gboolean +mate_mixer_pulse_open (MateMixerBackend *backend) +{ + int ret; + gchar *app_name; + MateMixerPulse *pulse; + + g_return_val_if_fail (MATE_MIXER_IS_BACKEND (backend), FALSE); + + pulse = MATE_MIXER_PULSE (backend); + + g_return_val_if_fail (pulse->priv->mainloop == NULL, FALSE); + + pulse->priv->mainloop = pa_threaded_mainloop_new (); + if (G_UNLIKELY (pulse->priv->mainloop == NULL)) { + g_warning ("Failed to created PulseAudio main loop"); + 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; + 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); + + return TRUE; +} + +void +mate_mixer_pulse_close (MateMixerBackend *backend) +{ + MateMixerPulse *pulse; + + g_return_if_fail (MATE_MIXER_IS_BACKEND (backend)); + + pulse = MATE_MIXER_PULSE (backend); + + g_return_if_fail (pulse->priv->mainloop != NULL); + + pa_threaded_mainloop_stop (pulse->priv->mainloop); + + pa_context_unref (pulse->priv->context); + pa_threaded_mainloop_free (pulse->priv->mainloop); + + pulse->priv->context = NULL; + pulse->priv->mainloop = NULL; +} + +GList * +mate_mixer_pulse_list_devices (MateMixerBackend *backend) +{ + MateMixerPulse *pulse; + pa_operation *o; + + g_return_val_if_fail (MATE_MIXER_IS_BACKEND (backend), NULL); + + pulse = MATE_MIXER_PULSE (backend); + + pa_threaded_mainloop_lock (pulse->priv->mainloop); + + 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; + } + + 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); + + return g_hash_table_get_values (pulse->priv->devices); +} + +static void +pulse_card_info_cb (pa_context *c, const pa_card_info *info, int eol, void *userdata) +{ + MateMixerPulse *pulse; + + pulse = MATE_MIXER_PULSE (userdata); + + if (!eol) + pulse_card_update (pulse, info); + + pa_threaded_mainloop_signal (pulse->priv->mainloop, 0); +} + +static void +pulse_card_update (MateMixerPulse *pulse, const pa_card_info *info) +{ + 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); + } +} + +static void +pulse_state_cb (pa_context *c, void *userdata) +{ + 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) +{ + // TODO +} + +static gchar * +pulse_get_app_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 ()); +} |