From caf4d9b8b8b0d26856d2d64f00ab23756867a923 Mon Sep 17 00:00:00 2001 From: Michal Ratajsky Date: Sat, 24 May 2014 00:15:24 +0200 Subject: Initial commit --- backends/Makefile.am | 7 + backends/null/Makefile.am | 24 +++ backends/null/null.c | 95 +++++++++++ backends/null/null.h | 54 ++++++ backends/pulse/Makefile.am | 30 ++++ backends/pulse/pulse-device.c | 308 +++++++++++++++++++++++++++++++++++ backends/pulse/pulse-device.h | 78 +++++++++ backends/pulse/pulse-track.c | 0 backends/pulse/pulse-track.h | 0 backends/pulse/pulse.c | 371 ++++++++++++++++++++++++++++++++++++++++++ backends/pulse/pulse.h | 61 +++++++ 11 files changed, 1028 insertions(+) create mode 100644 backends/Makefile.am create mode 100644 backends/null/Makefile.am create mode 100644 backends/null/null.c create mode 100644 backends/null/null.h create mode 100644 backends/pulse/Makefile.am create mode 100644 backends/pulse/pulse-device.c create mode 100644 backends/pulse/pulse-device.h create mode 100644 backends/pulse/pulse-track.c create mode 100644 backends/pulse/pulse-track.h create mode 100644 backends/pulse/pulse.c create mode 100644 backends/pulse/pulse.h (limited to 'backends') diff --git a/backends/Makefile.am b/backends/Makefile.am new file mode 100644 index 0000000..d2f0e96 --- /dev/null +++ b/backends/Makefile.am @@ -0,0 +1,7 @@ +SUBDIRS = null + +if HAVE_PULSEAUDIO +SUBDIRS += pulse +endif + +-include $(top_srcdir)/git.mk diff --git a/backends/null/Makefile.am b/backends/null/Makefile.am new file mode 100644 index 0000000..b6d92c7 --- /dev/null +++ b/backends/null/Makefile.am @@ -0,0 +1,24 @@ +backenddir = $(libdir)/libmatemixer + +backend_LTLIBRARIES = libmatemixer-null.la + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -DG_LOG_DOMAIN=\"libmatemixer-null\" + +libmatemixer_null_la_CFLAGS = \ + $(GLIB_CFLAGS) + +libmatemixer_null_la_SOURCES = \ + null.c \ + null.h + +libmatemixer_null_la_LIBADD = \ + $(GLIB_LIBS) + +libmatemixer_null_la_LDFLAGS = \ + -avoid-version \ + -export-dynamic \ + -module + +-include $(top_srcdir)/git.mk diff --git a/backends/null/null.c b/backends/null/null.c new file mode 100644 index 0000000..c049dcb --- /dev/null +++ b/backends/null/null.c @@ -0,0 +1,95 @@ +/* + * 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 "null.h" + +#define BACKEND_NAME "Null" +#define BACKEND_PRIORITY 999 + +/* 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); + +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; + +void +backend_module_init (GTypeModule *module) +{ + mate_mixer_null_register_type (module); + + info.name = BACKEND_NAME; + info.priority = BACKEND_PRIORITY; + info.g_type = MATE_MIXER_TYPE_NULL; + info.backend_type = MATE_MIXER_BACKEND_TYPE_NULL; +} + +void +backend_module_free (void) +{ +} + +const MateMixerBackendModuleInfo * +backend_module_get_info (void) +{ + return &info; +} + +static void +mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) +{ +} + +static void +mate_mixer_null_init (MateMixerNull *null) +{ +} + +static void +mate_mixer_null_finalize (GObject *object) +{ + G_OBJECT_CLASS (mate_mixer_null_parent_class)->finalize (object); +} + +static void +mate_mixer_null_class_init (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) +{ +} diff --git a/backends/null/null.h b/backends/null/null.h new file mode 100644 index 0000000..ef5b779 --- /dev/null +++ b/backends/null/null.h @@ -0,0 +1,54 @@ +/* + * 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 . + */ + +#ifndef MATEMIXER_NULL_H +#define MATEMIXER_NULL_H + +#include +#include + +#include + +#define MATE_MIXER_TYPE_NULL \ + (mate_mixer_null_get_type ()) +#define MATE_MIXER_NULL(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), MATE_MIXER_TYPE_NULL, MateMixerNull)) +#define MATE_MIXER_IS_NULL(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), MATE_MIXER_TYPE_NULL)) +#define MATE_MIXER_NULL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), MATE_MIXER_TYPE_NULL, MateMixerNullClass)) +#define MATE_MIXER_IS_NULL_CLASS(k) \ + (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), MATE_MIXER_TYPE_NULL)) +#define MATE_MIXER_NULL_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), MATE_MIXER_TYPE_NULL, MateMixerNullClass)) + +typedef struct _MateMixerNull MateMixerNull; +typedef struct _MateMixerNullClass MateMixerNullClass; + +struct _MateMixerNull +{ + GObject parent; +}; + +struct _MateMixerNullClass +{ + GObjectClass parent; +}; + +GType mate_mixer_null_get_type (void) G_GNUC_CONST; + +#endif /* MATEMIXER_NULL_H */ diff --git a/backends/pulse/Makefile.am b/backends/pulse/Makefile.am new file mode 100644 index 0000000..2f7cd6f --- /dev/null +++ b/backends/pulse/Makefile.am @@ -0,0 +1,30 @@ +backenddir = $(libdir)/libmatemixer + +backend_LTLIBRARIES = libmatemixer-pulse.la + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -DG_LOG_DOMAIN=\"libmatemixer-pulse\" + +libmatemixer_pulse_la_CFLAGS = \ + $(GLIB_CFLAGS) \ + $(PULSEAUDIO_CFLAGS) + +libmatemixer_pulse_la_SOURCES = \ + pulse.c \ + pulse.h \ + pulse-device.c \ + pulse-device.h \ + pulse-track.c \ + pulse-track.h + +libmatemixer_pulse_la_LIBADD = \ + $(GLIB_LIBS) \ + $(PULSEAUDIO_LIBS) + +libmatemixer_pulse_la_LDFLAGS = \ + -avoid-version \ + -export-dynamic \ + -module + +-include $(top_srcdir)/git.mk diff --git a/backends/pulse/pulse-device.c b/backends/pulse/pulse-device.c new file mode 100644 index 0000000..75c5a32 --- /dev/null +++ b/backends/pulse/pulse-device.c @@ -0,0 +1,308 @@ +/* + * 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 "pulse-device.h" + +struct _MateMixerPulseDevicePrivate +{ + guint32 index; + GList *profiles; + GList *ports; + gchar *identifier; + gchar *name; + gchar *icon; + + MateMixerDeviceProfile *active_profile; +}; + +enum +{ + PROP_0, + PROP_IDENTIFIER, + PROP_NAME, + PROP_ICON, + PROP_ACTIVE_PROFILE, + N_PROPERTIES +}; + +static void mate_mixer_device_interface_init (MateMixerDeviceInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (MateMixerPulseDevice, mate_mixer_pulse_device, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_DEVICE, + mate_mixer_device_interface_init)) + +static void +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); +} + +static void +mate_mixer_pulse_device_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + MateMixerPulseDevice *device; + + 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_ICON: + g_value_set_string (value, device->priv->icon); + break; + case PROP_ACTIVE_PROFILE: + g_value_set_object (value, device->priv->active_profile); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +mate_mixer_pulse_device_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + MateMixerPulseDevice *device; + + 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_ICON: + device->priv->icon = g_strdup (g_value_get_string (value)); + break; + case PROP_ACTIVE_PROFILE: + // TODO + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +mate_mixer_pulse_device_finalize (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) + g_list_free_full (device->priv->profiles, g_object_unref); + + if (device->priv->ports != NULL) + g_list_free_full (device->priv->ports, g_object_unref); + + if (device->priv->active_profile != NULL) + g_object_unref (device->priv->active_profile); + + G_OBJECT_CLASS (mate_mixer_pulse_device_parent_class)->finalize (object); +} + +static void +mate_mixer_pulse_device_class_init (MateMixerPulseDeviceClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + 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_ICON, "icon"); + g_object_class_override_property (object_class, PROP_ACTIVE_PROFILE, "active-profile"); + + g_type_class_add_private (object_class, sizeof (MateMixerPulseDevicePrivate)); +} + +MateMixerPulseDevice * +mate_mixer_pulse_device_new (const pa_card_info *info) +{ + MateMixerPulseDevice *device; + MateMixerDeviceProfile *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; + +#if PA_CHECK_VERSION(5, 0, 0) + pa_card_profile_info2 *p_info = info->profiles2[i]; + + /* PulseAudio 5.0 includes a new pa_card_profile_info2 which + * only differs in the new available flag, we use it not to include + * profiles which are unavailable */ + if (p_info->available == 0) + continue; +#else + /* The old profile list is an array of structs, not pointers */ + pa_card_profile_info *p_info = &info->profiles[i]; +#endif + profile = mate_mixer_device_profile_new ( + p_info->name, + p_info->description, + p_info->priority); + +#if PA_CHECK_VERSION(5, 0, 0) + if (!g_strcmp0 (p_info->name, info->active_profile2->name)) + active_profile = g_object_ref (profile); +#else + if (!g_strcmp0 (p_info->name, info->active_profile->name)) + active_profile = g_object_ref (profile); +#endif + profiles = g_list_prepend (profiles, profile); + } + + /* Keep the profiles in the same order as in PulseAudio */ + if (profiles) + profiles = g_list_reverse (profiles); + + /* 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; + +#if PA_CHECK_VERSION(2, 0, 0) + if (p_info->available == PA_PORT_AVAILABLE_YES) + status |= MATE_MIXER_DEVICE_PORT_STATUS_AVAILABLE; +#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, + p_info->latency_offset); + + ports = g_list_prepend (ports, port); + } + + /* Keep the ports in the same order as in PulseAudio */ + if (ports) + 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); + + device->priv->index = info->index; + device->priv->profiles = profiles; + device->priv->ports = ports; + + return device; +} + +const GList * +mate_mixer_pulse_device_get_ports (MateMixerPulseDevice *device) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL); + + return device->priv->ports; +} + +const GList * +mate_mixer_pulse_device_get_profiles (MateMixerPulseDevice *device) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL); + + return device->priv->profiles; +} + +MateMixerDeviceProfile * +mate_mixer_pulse_device_get_active_profile (MateMixerPulseDevice *device) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), NULL); + + return device->priv->active_profile; +} + +gboolean +mate_mixer_pulse_device_set_active_profile (MateMixerPulseDevice *device, + MateMixerDeviceProfile *profile) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), FALSE); + g_return_val_if_fail (MATE_MIXER_IS_DEVICE_PROFILE (profile), FALSE); + + // TODO + // pa_context_set_card_profile_by_index () + return TRUE; +} + +gboolean +mate_mixer_pulse_device_update (MateMixerPulseDevice *device, const pa_card_info *info) +{ + g_return_val_if_fail (MATE_MIXER_IS_PULSE_DEVICE (device), FALSE); + g_return_val_if_fail (info != NULL, FALSE); + + // TODO: update status, active_profile, maybe others? + return TRUE; +} diff --git a/backends/pulse/pulse-device.h b/backends/pulse/pulse-device.h new file mode 100644 index 0000000..f8b5a07 --- /dev/null +++ b/backends/pulse/pulse-device.h @@ -0,0 +1,78 @@ +/* + * 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 . + */ + +#ifndef MATEMIXER_PULSE_DEVICE_H +#define MATEMIXER_PULSE_DEVICE_H + +#include +#include + +#include +#include + +#include + +G_BEGIN_DECLS + +#define MATE_MIXER_TYPE_PULSE_DEVICE \ + (mate_mixer_pulse_device_get_type ()) +#define MATE_MIXER_PULSE_DEVICE(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), MATE_MIXER_TYPE_PULSE_DEVICE, MateMixerPulseDevice)) +#define MATE_MIXER_IS_PULSE_DEVICE(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), MATE_MIXER_TYPE_PULSE_DEVICE)) +#define MATE_MIXER_PULSE_DEVICE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), MATE_MIXER_TYPE_PULSE_DEVICE, MateMixerPulseDeviceClass)) +#define MATE_MIXER_IS_PULSE_DEVICE_CLASS(k) \ + (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), MATE_MIXER_TYPE_PULSE_DEVICE)) +#define MATE_MIXER_PULSE_DEVICE_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), MATE_MIXER_TYPE_PULSE_DEVICE, MateMixerPulseDeviceClass)) + +typedef struct _MateMixerPulseDevice MateMixerPulseDevice; +typedef struct _MateMixerPulseDeviceClass MateMixerPulseDeviceClass; +typedef struct _MateMixerPulseDevicePrivate MateMixerPulseDevicePrivate; + +struct _MateMixerPulseDevice +{ + GObject parent; + + MateMixerPulseDevicePrivate *priv; +}; + +struct _MateMixerPulseDeviceClass +{ + GObjectClass parent; +}; + +GType mate_mixer_pulse_device_get_type (void) G_GNUC_CONST; + +MateMixerPulseDevice *mate_mixer_pulse_device_new (const pa_card_info *info); +GList *mate_mixer_pulse_device_list_tracks (MateMixerPulseDevice *device); + +const GList *mate_mixer_pulse_device_get_ports (MateMixerPulseDevice *device); +const GList *mate_mixer_pulse_device_get_profiles (MateMixerPulseDevice *device); + +MateMixerDeviceProfile *mate_mixer_pulse_device_get_active_profile (MateMixerPulseDevice *device); + +gboolean mate_mixer_pulse_device_set_active_profile (MateMixerPulseDevice *device, + MateMixerDeviceProfile *profile); + +gboolean mate_mixer_pulse_device_update (MateMixerPulseDevice *device, + const pa_card_info *info); + +G_END_DECLS + +#endif /* MATEMIXER_PULSE_DEVICE_H */ diff --git a/backends/pulse/pulse-track.c b/backends/pulse/pulse-track.c new file mode 100644 index 0000000..e69de29 diff --git a/backends/pulse/pulse-track.h b/backends/pulse/pulse-track.h new file mode 100644 index 0000000..e69de29 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 + * + * 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); +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 ()); +} diff --git a/backends/pulse/pulse.h b/backends/pulse/pulse.h new file mode 100644 index 0000000..2f8414f --- /dev/null +++ b/backends/pulse/pulse.h @@ -0,0 +1,61 @@ +/* + * 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 . + */ + +#ifndef MATEMIXER_PULSE_H +#define MATEMIXER_PULSE_H + +#include +#include + +#include + +#define MATE_MIXER_TYPE_PULSE \ + (mate_mixer_pulse_get_type ()) +#define MATE_MIXER_PULSE(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), MATE_MIXER_TYPE_PULSE, MateMixerPulse)) +#define MATE_MIXER_IS_PULSE(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), MATE_MIXER_TYPE_PULSE)) +#define MATE_MIXER_PULSE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), MATE_MIXER_TYPE_PULSE, MateMixerPulseClass)) +#define MATE_MIXER_IS_PULSE_CLASS(k) \ + (G_TYPE_CLASS_CHECK_CLASS_TYPE ((k), MATE_MIXER_TYPE_PULSE)) +#define MATE_MIXER_PULSE_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), MATE_MIXER_TYPE_PULSE, MateMixerPulseClass)) + +typedef struct _MateMixerPulse MateMixerPulse; +typedef struct _MateMixerPulseClass MateMixerPulseClass; +typedef struct _MateMixerPulsePrivate MateMixerPulsePrivate; + +struct _MateMixerPulse +{ + GObject parent; + + MateMixerPulsePrivate *priv; +}; + +struct _MateMixerPulseClass +{ + GObjectClass parent; +}; + +GType mate_mixer_pulse_get_type (void) G_GNUC_CONST; + +gboolean mate_mixer_pulse_open (MateMixerBackend *backend); +void mate_mixer_pulse_close (MateMixerBackend *backend); +GList *mate_mixer_pulse_list_devices (MateMixerBackend *backend); + +#endif /* MATEMIXER_PULSE_H */ -- cgit v1.2.1