/* * Copyright (C) 2014 Michal Ratajsky * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the licence, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . */ #include #include #include #include #include #include #include #include #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); 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; } 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 ()); }