diff options
Diffstat (limited to 'backends/alsa')
23 files changed, 4577 insertions, 0 deletions
diff --git a/backends/alsa/Makefile.am b/backends/alsa/Makefile.am new file mode 100644 index 0000000..220bb3b --- /dev/null +++ b/backends/alsa/Makefile.am @@ -0,0 +1,47 @@ +backenddir = $(libdir)/libmatemixer + +backend_LTLIBRARIES = libmatemixer-alsa.la + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -DG_LOG_DOMAIN=\"libmatemixer-alsa\" + +libmatemixer_alsa_la_CFLAGS = \ + $(GLIB_CFLAGS) \ + $(ALSA_CFLAGS) + +libmatemixer_alsa_la_SOURCES = \ + alsa-backend.c \ + alsa-backend.h \ + alsa-constants.c \ + alsa-constants.h \ + alsa-device.c \ + alsa-device.h \ + alsa-element.c \ + alsa-element.h \ + alsa-stream.c \ + alsa-stream.h \ + alsa-stream-control.c \ + alsa-stream-control.h \ + alsa-stream-input-control.c \ + alsa-stream-input-control.h \ + alsa-stream-output-control.c \ + alsa-stream-output-control.h \ + alsa-switch.c \ + alsa-switch.h \ + alsa-switch-option.c \ + alsa-switch-option.h \ + alsa-toggle.c \ + alsa-toggle.h + +libmatemixer_alsa_la_LIBADD = \ + $(GLIB_LIBS) \ + $(ALSA_LIBS) + +libmatemixer_alsa_la_LDFLAGS = \ + -avoid-version \ + -no-undefined \ + -export-dynamic \ + -module + +-include $(top_srcdir)/git.mk diff --git a/backends/alsa/alsa-backend.c b/backends/alsa/alsa-backend.c new file mode 100644 index 0000000..7a17b85 --- /dev/null +++ b/backends/alsa/alsa-backend.c @@ -0,0 +1,508 @@ +/* + * 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 <alsa/asoundlib.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "alsa-backend.h" +#include "alsa-device.h" +#include "alsa-stream.h" + +#define BACKEND_NAME "ALSA" +#define BACKEND_PRIORITY 9 + +struct _AlsaBackendPrivate +{ + GSource *timeout_source; + GHashTable *devices; + GHashTable *devices_ids; +}; + +static void alsa_backend_class_init (AlsaBackendClass *klass); +static void alsa_backend_class_finalize (AlsaBackendClass *klass); +static void alsa_backend_init (AlsaBackend *alsa); +static void alsa_backend_dispose (GObject *object); +static void alsa_backend_finalize (GObject *object); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +G_DEFINE_DYNAMIC_TYPE (AlsaBackend, alsa_backend, MATE_MIXER_TYPE_BACKEND) +#pragma clang diagnostic pop + +static gboolean alsa_backend_open (MateMixerBackend *backend); +static void alsa_backend_close (MateMixerBackend *backend); +static GList * alsa_backend_list_devices (MateMixerBackend *backend); +static GList * alsa_backend_list_streams (MateMixerBackend *backend); + +static gboolean read_devices (AlsaBackend *alsa); + +static gboolean read_device (AlsaBackend *alsa, + const gchar *card); + +static void add_device (AlsaBackend *alsa, + AlsaDevice *device); + +static void remove_device (AlsaBackend *alsa, + AlsaDevice *device); +static void remove_stream (AlsaBackend *alsa, + const gchar *name); + +static void select_default_input_stream (AlsaBackend *alsa); +static void select_default_output_stream (AlsaBackend *alsa); + +static MateMixerBackendInfo info; + +void +backend_module_init (GTypeModule *module) +{ + alsa_backend_register_type (module); + + info.name = BACKEND_NAME; + info.priority = BACKEND_PRIORITY; + info.g_type = ALSA_TYPE_BACKEND; + info.backend_type = MATE_MIXER_BACKEND_ALSA; +} + +const MateMixerBackendInfo *backend_module_get_info (void) +{ + return &info; +} + +static void +alsa_backend_class_init (AlsaBackendClass *klass) +{ + GObjectClass *object_class; + MateMixerBackendClass *backend_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = alsa_backend_dispose; + object_class->finalize = alsa_backend_finalize; + + backend_class = MATE_MIXER_BACKEND_CLASS (klass); + backend_class->open = alsa_backend_open; + backend_class->close = alsa_backend_close; + backend_class->list_devices = alsa_backend_list_devices; + backend_class->list_streams = alsa_backend_list_streams; + + g_type_class_add_private (object_class, sizeof (AlsaBackendPrivate)); +} + +/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE() */ +static void +alsa_backend_class_finalize (AlsaBackendClass *klass) +{ +} + +static void +alsa_backend_init (AlsaBackend *alsa) +{ + alsa->priv = G_TYPE_INSTANCE_GET_PRIVATE (alsa, + ALSA_TYPE_BACKEND, + AlsaBackendPrivate); + + alsa->priv->devices = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + alsa->priv->devices_ids = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); +} + +static void +alsa_backend_dispose (GObject *object) +{ + MateMixerBackend *backend; + MateMixerState state; + + backend = MATE_MIXER_BACKEND (object); + + state = mate_mixer_backend_get_state (backend); + if (state != MATE_MIXER_STATE_IDLE) + alsa_backend_close (backend); + + G_OBJECT_CLASS (alsa_backend_parent_class)->dispose (object); +} + +static void +alsa_backend_finalize (GObject *object) +{ + AlsaBackend *alsa; + + alsa = ALSA_BACKEND (object); + + g_hash_table_unref (alsa->priv->devices); + g_hash_table_unref (alsa->priv->devices_ids); + + G_OBJECT_CLASS (alsa_backend_parent_class)->finalize (object); +} + +static gboolean +alsa_backend_open (MateMixerBackend *backend) +{ + AlsaBackend *alsa; + + g_return_val_if_fail (ALSA_IS_BACKEND (backend), FALSE); + + alsa = ALSA_BACKEND (backend); + + /* Poll ALSA for changes every 500 milliseconds, this actually only + * discovers added or changed sound cards, sound card related events + * are handled by AlsaDevices */ + alsa->priv->timeout_source = g_timeout_source_new_seconds (1); + g_source_set_callback (alsa->priv->timeout_source, + (GSourceFunc) read_devices, + alsa, + NULL); + g_source_attach (alsa->priv->timeout_source, + g_main_context_get_thread_default ()); + + /* Read the initial list of devices so we have some starting point, there + * isn't really a way to detect errors here, failing to add a device may + * be a device-related problem so make the backend always open successfully */ + read_devices (alsa); + + _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_READY); + return TRUE; +} + +void +alsa_backend_close (MateMixerBackend *backend) +{ + AlsaBackend *alsa; + + g_return_if_fail (ALSA_IS_BACKEND (backend)); + + alsa = ALSA_BACKEND (backend); + + g_source_destroy (alsa->priv->timeout_source); + + g_hash_table_remove_all (alsa->priv->devices); + g_hash_table_remove_all (alsa->priv->devices_ids); + + _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_IDLE); +} + +static GList * +alsa_backend_list_devices (MateMixerBackend *backend) +{ + GList *list; + + g_return_val_if_fail (ALSA_IS_BACKEND (backend), NULL); + + /* Convert the hash table to a linked list, this list is expected to be + * cached in the main library */ + list = g_hash_table_get_values (ALSA_BACKEND (backend)->priv->devices); + if (list != NULL) + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return list; +} + +static GList * +alsa_backend_list_streams (MateMixerBackend *backend) +{ + AlsaBackend *alsa; + GHashTableIter iter; + gpointer value; + GList *list = NULL; + + g_return_val_if_fail (ALSA_IS_BACKEND (backend), NULL); + + alsa = ALSA_BACKEND (backend); + + /* We don't keep a list or hash table of all streams here, instead walk + * through the list of devices and create the list manually, each device + * has at most one input and one output stream */ + g_hash_table_iter_init (&iter, alsa->priv->devices); + + while (g_hash_table_iter_next (&iter, NULL, &value)) { + AlsaDevice *device = ALSA_DEVICE (value); + AlsaStream *stream; + + stream = alsa_device_get_output_stream (device); + if (stream != NULL) + list = g_list_prepend (list, stream); + stream = alsa_device_get_input_stream (device); + if (stream != NULL) + list = g_list_prepend (list, stream); + } + + if (list != NULL) + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return list; +} + +static gboolean +read_devices (AlsaBackend *alsa) +{ + gint num; + gint ret; + gchar card[16]; + gboolean changed = FALSE; + + /* Read the default device first, it will be either one of the hardware cards + * that will be queried later, or a software mixer */ + if (read_device (alsa, "default") == TRUE) + changed = TRUE; + + for (num = -1;;) { + /* Read number of the next sound card */ + ret = snd_card_next (&num); + if (ret < 0 || + num < 0) + break; + + g_snprintf (card, sizeof (card), "hw:%d", num); + + if (read_device (alsa, card) == TRUE) + changed = TRUE; + } + + /* If any card has been added, make sure we have the most suitable default + * input and output streams */ + if (changed == TRUE) { + select_default_input_stream (alsa); + select_default_output_stream (alsa); + } + return G_SOURCE_CONTINUE; +} + +static gboolean +read_device (AlsaBackend *alsa, const gchar *card) +{ + AlsaDevice *device; + snd_ctl_t *ctl; + snd_ctl_card_info_t *info; + const gchar *id; + gint ret; + + /* The device may be already known, remove it if it's known and fails + * to be read, this happens for example when PulseAudio is killed */ + device = g_hash_table_lookup (alsa->priv->devices, card); + + ret = snd_ctl_open (&ctl, card, 0); + if (ret < 0) { + g_warning ("Failed to open ALSA control for %s: %s", + card, + snd_strerror (ret)); + if (device != NULL) + remove_device (alsa, device); + return FALSE; + } + + snd_ctl_card_info_alloca (&info); + + ret = snd_ctl_card_info (ctl, info); + if (ret < 0) { + g_warning ("Failed to read card info: %s", snd_strerror (ret)); + if (device != NULL) + remove_device (alsa, device); + + snd_ctl_close (ctl); + return FALSE; + } + + id = snd_ctl_card_info_get_id (info); + + /* We also keep a list of device identifiers to be sure no card is + * added twice, this could commonly happen because some card may + * also be assigned to the "default" ALSA device */ + if (g_hash_table_contains (alsa->priv->devices_ids, id) == TRUE) { + snd_ctl_close (ctl); + return FALSE; + } + + device = alsa_device_new (card, snd_ctl_card_info_get_name (info)); + + if (alsa_device_open (device) == FALSE) { + g_object_unref (device); + snd_ctl_close (ctl); + return FALSE; + } + + g_object_set_data_full (G_OBJECT (device), + "__matemixer_alsa_device_id", + g_strdup (id), + g_free); + + add_device (alsa, device); + + snd_ctl_close (ctl); + return TRUE; +} + +static void +add_device (AlsaBackend *alsa, AlsaDevice *device) +{ + const gchar *name; + + name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + + g_hash_table_insert (alsa->priv->devices, + g_strdup (name), + g_object_ref (device)); + + /* Remember the device identifier, use a single string copy as we only free + * the hash table key */ + g_hash_table_add (alsa->priv->devices_ids, + g_strdup (g_object_get_data (G_OBJECT (device), + "__matemixer_alsa_device_id"))); + + g_signal_connect_swapped (G_OBJECT (device), + "closed", + G_CALLBACK (remove_device), + alsa); + g_signal_connect_swapped (G_OBJECT (device), + "stream-removed", + G_CALLBACK (remove_stream), + alsa); + + g_signal_emit_by_name (G_OBJECT (alsa), "device-added", name); + + /* Load the device elements after emitting device-added, because the load + * function will most likely emit stream-added on the device and backend */ + alsa_device_load (device); +} + +static void +remove_device (AlsaBackend *alsa, AlsaDevice *device) +{ + const gchar *name; + + name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + + g_signal_handlers_disconnect_by_func (G_OBJECT (device), + G_CALLBACK (remove_device), + alsa); + g_signal_handlers_disconnect_by_func (G_OBJECT (device), + G_CALLBACK (remove_stream), + alsa); + + /* Remove the device */ + g_hash_table_remove (alsa->priv->devices_ids, + g_object_get_data (G_OBJECT (device), + "__matemixer_alsa_device_id")); + + // XXX close the device and make it remove streams + g_hash_table_remove (alsa->priv->devices, name); + g_signal_emit_by_name (G_OBJECT (alsa), + "device-removed", + name); +} + +static void +remove_stream (AlsaBackend *alsa, const gchar *name) +{ + MateMixerStream *stream; + + stream = mate_mixer_backend_get_default_input_stream (MATE_MIXER_BACKEND (alsa)); + + // XXX see if the change happens after stream is removed or before + if (stream != NULL && strcmp (mate_mixer_stream_get_name (stream), name) == 0) + select_default_input_stream (alsa); + + stream = mate_mixer_backend_get_default_output_stream (MATE_MIXER_BACKEND (alsa)); + + if (stream != NULL && strcmp (mate_mixer_stream_get_name (stream), name) == 0) + select_default_output_stream (alsa); +} + +static void +select_default_input_stream (AlsaBackend *alsa) +{ + AlsaDevice *device; + AlsaStream *stream; + gchar card[16]; + gint num; + + /* Always prefer stream in the "default" device */ + device = g_hash_table_lookup (alsa->priv->devices, "default"); + if (device != NULL) { + stream = alsa_device_get_input_stream (device); + if (stream != NULL) { + _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (alsa), + MATE_MIXER_STREAM (stream)); + return; + } + } + + /* If there is no input stream in the default device, search the cards in + * the correct order */ + for (num = 0;; num++) { + g_snprintf (card, sizeof (card), "hw:%d", num); + + device = g_hash_table_lookup (alsa->priv->devices, card); + if (device == NULL) + break; + stream = alsa_device_get_input_stream (device); + if (stream != NULL) { + _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (alsa), + MATE_MIXER_STREAM (stream)); + return; + } + } + + /* In the worst case unset the default stream */ + _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (alsa), NULL); +} + +static void +select_default_output_stream (AlsaBackend *alsa) +{ + AlsaDevice *device; + AlsaStream *stream; + gchar card[16]; + gint num; + + /* Always prefer stream in the "default" device */ + device = g_hash_table_lookup (alsa->priv->devices, "default"); + if (device != NULL) { + stream = alsa_device_get_output_stream (device); + if (stream != NULL) { + _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (alsa), + MATE_MIXER_STREAM (stream)); + return; + } + } + + /* If there is no input stream in the default device, search the cards in + * the correct order */ + for (num = 0;; num++) { + g_snprintf (card, sizeof (card), "hw:%d", num); + + device = g_hash_table_lookup (alsa->priv->devices, card); + if (device == NULL) + break; + stream = alsa_device_get_output_stream (device); + if (stream != NULL) { + _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (alsa), + MATE_MIXER_STREAM (stream)); + return; + } + } + + /* In the worst case unset the default stream */ + _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (alsa), NULL); +} diff --git a/backends/alsa/alsa-backend.h b/backends/alsa/alsa-backend.h new file mode 100644 index 0000000..03fedf0 --- /dev/null +++ b/backends/alsa/alsa-backend.h @@ -0,0 +1,62 @@ +/* + * 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/>. + */ + +#ifndef ALSA_BACKEND_H +#define ALSA_BACKEND_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#define ALSA_TYPE_BACKEND \ + (alsa_backend_get_type ()) +#define ALSA_BACKEND(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_BACKEND, AlsaBackend)) +#define ALSA_IS_BACKEND(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_BACKEND)) +#define ALSA_BACKEND_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), ALSA_TYPE_BACKEND, AlsaBackendClass)) +#define ALSA_IS_BACKEND_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), ALSA_TYPE_BACKEND)) +#define ALSA_BACKEND_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), ALSA_TYPE_BACKEND, AlsaBackendClass)) + +typedef struct _AlsaBackend AlsaBackend; +typedef struct _AlsaBackendClass AlsaBackendClass; +typedef struct _AlsaBackendPrivate AlsaBackendPrivate; + +struct _AlsaBackend +{ + MateMixerBackend parent; + + /*< private >*/ + AlsaBackendPrivate *priv; +}; + +struct _AlsaBackendClass +{ + MateMixerBackendClass parent_class; +}; + +GType alsa_backend_get_type (void) G_GNUC_CONST; + +/* Support function for dynamic loading of the backend module */ +void backend_module_init (GTypeModule *module); +const MateMixerBackendInfo *backend_module_get_info (void); + +#endif /* ALSA_BACKEND_H */ diff --git a/backends/alsa/alsa-constants.c b/backends/alsa/alsa-constants.c new file mode 100644 index 0000000..2124a2e --- /dev/null +++ b/backends/alsa/alsa-constants.c @@ -0,0 +1,72 @@ +/* + * 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/gi18n.h> +#include <alsa/asoundlib.h> +#include <libmatemixer/matemixer.h> + +#include "alsa-constants.h" + +// XXX add more and probably move them somewhere else +const AlsaControlInfo alsa_controls[] = +{ + { "Master", N_("Master"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER }, + { "Speaker", N_("Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER }, + { "Capture", N_("Capture"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER }, + { "PCM", N_("PCM"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM }, + { "Line", N_("Line"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT }, + { "Mic", N_("Mic"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT }, + { NULL } +}; + +const MateMixerChannelPosition alsa_channel_map_from[SND_MIXER_SCHN_LAST] = +{ + [SND_MIXER_SCHN_FRONT_LEFT] = MATE_MIXER_CHANNEL_FRONT_LEFT, + [SND_MIXER_SCHN_FRONT_RIGHT] = MATE_MIXER_CHANNEL_FRONT_RIGHT, + [SND_MIXER_SCHN_REAR_LEFT] = MATE_MIXER_CHANNEL_BACK_LEFT, + [SND_MIXER_SCHN_REAR_RIGHT] = MATE_MIXER_CHANNEL_BACK_RIGHT, + [SND_MIXER_SCHN_FRONT_CENTER] = MATE_MIXER_CHANNEL_FRONT_CENTER, + [SND_MIXER_SCHN_WOOFER] = MATE_MIXER_CHANNEL_LFE, + [SND_MIXER_SCHN_SIDE_LEFT] = MATE_MIXER_CHANNEL_SIDE_LEFT, + [SND_MIXER_SCHN_SIDE_RIGHT] = MATE_MIXER_CHANNEL_SIDE_RIGHT, + [SND_MIXER_SCHN_REAR_CENTER] = MATE_MIXER_CHANNEL_BACK_CENTER +}; + +const snd_mixer_selem_channel_id_t alsa_channel_map_to[MATE_MIXER_CHANNEL_MAX] = +{ + [MATE_MIXER_CHANNEL_UNKNOWN] = SND_MIXER_SCHN_UNKNOWN, + [MATE_MIXER_CHANNEL_MONO] = SND_MIXER_SCHN_MONO, + [MATE_MIXER_CHANNEL_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT, + [MATE_MIXER_CHANNEL_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT, + [MATE_MIXER_CHANNEL_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER, + [MATE_MIXER_CHANNEL_LFE] = SND_MIXER_SCHN_WOOFER, + [MATE_MIXER_CHANNEL_BACK_LEFT] = SND_MIXER_SCHN_REAR_LEFT, + [MATE_MIXER_CHANNEL_BACK_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT, + [MATE_MIXER_CHANNEL_BACK_CENTER] = SND_MIXER_SCHN_REAR_CENTER, + [MATE_MIXER_CHANNEL_FRONT_LEFT_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [MATE_MIXER_CHANNEL_FRONT_RIGHT_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [MATE_MIXER_CHANNEL_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT, + [MATE_MIXER_CHANNEL_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT, + [MATE_MIXER_CHANNEL_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN, + [MATE_MIXER_CHANNEL_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN, + [MATE_MIXER_CHANNEL_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [MATE_MIXER_CHANNEL_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [MATE_MIXER_CHANNEL_TOP_BACK_LEFT] = SND_MIXER_SCHN_UNKNOWN, + [MATE_MIXER_CHANNEL_TOP_BACK_RIGHT] = SND_MIXER_SCHN_UNKNOWN, + [MATE_MIXER_CHANNEL_TOP_BACK_CENTER] = SND_MIXER_SCHN_UNKNOWN +}; diff --git a/backends/alsa/alsa-constants.h b/backends/alsa/alsa-constants.h new file mode 100644 index 0000000..81257c7 --- /dev/null +++ b/backends/alsa/alsa-constants.h @@ -0,0 +1,35 @@ +/* + * 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/>. + */ + +#ifndef ALSA_CONSTANTS_H +#define ALSA_CONSTANTS_H + +#include <glib.h> +#include <alsa/asoundlib.h> +#include <libmatemixer/matemixer.h> + +typedef struct { + gchar *name; + gchar *label; + MateMixerStreamControlRole role; +} AlsaControlInfo; + +extern const AlsaControlInfo alsa_controls[]; +extern const MateMixerChannelPosition alsa_channel_map_from[]; +extern const snd_mixer_selem_channel_id_t alsa_channel_map_to[]; + +#endif /* ALSA_CONSTANTS_H */ diff --git a/backends/alsa/alsa-device.c b/backends/alsa/alsa-device.c new file mode 100644 index 0000000..5acc6f5 --- /dev/null +++ b/backends/alsa/alsa-device.c @@ -0,0 +1,941 @@ +/* + * 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 <strings.h> +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <alsa/asoundlib.h> +#include <libmatemixer/matemixer.h> + +#include "alsa-constants.h" +#include "alsa-device.h" +#include "alsa-element.h" +#include "alsa-stream.h" +#include "alsa-stream-control.h" +#include "alsa-stream-input-control.h" +#include "alsa-stream-output-control.h" +#include "alsa-switch.h" +#include "alsa-switch-option.h" +#include "alsa-toggle.h" + +#define ALSA_DEVICE_ICON "audio-card" + +struct _AlsaDevicePrivate +{ + snd_mixer_t *handle; + GMainContext *context; + GMutex mutex; + GCond cond; + AlsaStream *input; + AlsaStream *output; + GHashTable *switches; + gboolean events_pending; +}; + +enum { + CLOSED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = { 0, }; + +static void alsa_device_class_init (AlsaDeviceClass *klass); +static void alsa_device_init (AlsaDevice *device); +static void alsa_device_dispose (GObject *object); +static void alsa_device_finalize (GObject *object); + +G_DEFINE_TYPE (AlsaDevice, alsa_device, MATE_MIXER_TYPE_DEVICE) + +static MateMixerSwitch *alsa_device_get_switch (MateMixerDevice *mmd, + const gchar *name); + +static GList * alsa_device_list_streams (MateMixerDevice *mmd); +static GList * alsa_device_list_switches (MateMixerDevice *mmd); + +static gboolean add_stream_input_control (AlsaDevice *device, + snd_mixer_elem_t *el); +static gboolean add_stream_output_control (AlsaDevice *device, + snd_mixer_elem_t *el); + +static gboolean add_switch (AlsaDevice *device, + AlsaStream *stream, + snd_mixer_elem_t *el); + +static gboolean add_device_switch (AlsaDevice *device, + snd_mixer_elem_t *el); + +static gboolean add_stream_input_switch (AlsaDevice *device, + snd_mixer_elem_t *el); +static gboolean add_stream_output_switch (AlsaDevice *device, + snd_mixer_elem_t *el); + +static gboolean add_stream_input_toggle (AlsaDevice *device, + snd_mixer_elem_t *el); +static gboolean add_stream_output_toggle (AlsaDevice *device, + snd_mixer_elem_t *el); + +static void load_element (AlsaDevice *device, + snd_mixer_elem_t *el); + +static void load_elements_by_name (AlsaDevice *device, + const gchar *name); + +static void remove_elements_by_name (AlsaDevice *device, + const gchar *name); + +static void handle_poll (AlsaDevice *device); + +static gboolean handle_process_events (AlsaDevice *device); + +static int handle_callback (snd_mixer_t *handle, + guint mask, + snd_mixer_elem_t *el); +static int handle_element_callback (snd_mixer_elem_t *el, + guint mask); + +static void close_device (AlsaDevice *device); + +static gchar * get_element_name (snd_mixer_elem_t *el); +static void get_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role); + +static void get_switch_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label); + +static void +alsa_device_class_init (AlsaDeviceClass *klass) +{ + GObjectClass *object_class; + MateMixerDeviceClass *device_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = alsa_device_dispose; + object_class->finalize = alsa_device_finalize; + + device_class = MATE_MIXER_DEVICE_CLASS (klass); + device_class->get_switch = alsa_device_get_switch; + device_class->list_streams = alsa_device_list_streams; + device_class->list_switches = alsa_device_list_switches; + + signals[CLOSED] = + g_signal_new ("closed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (AlsaDeviceClass, closed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0, + G_TYPE_NONE); + + g_type_class_add_private (object_class, sizeof (AlsaDevicePrivate)); +} + +static void +alsa_device_init (AlsaDevice *device) +{ + device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device, + ALSA_TYPE_DEVICE, + AlsaDevicePrivate); + + device->priv->switches = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + device->priv->context = g_main_context_ref_thread_default (); + + g_mutex_init (&device->priv->mutex); + g_cond_init (&device->priv->cond); +} + +static void +alsa_device_dispose (GObject *object) +{ + AlsaDevice *device; + + device = ALSA_DEVICE (object); + + g_clear_object (&device->priv->input); + g_clear_object (&device->priv->output); + + g_hash_table_remove_all (device->priv->switches); + + G_OBJECT_CLASS (alsa_device_parent_class)->dispose (object); +} + +static void +alsa_device_finalize (GObject *object) +{ + AlsaDevice *device; + + device = ALSA_DEVICE (object); + + g_mutex_clear (&device->priv->mutex); + g_cond_clear (&device->priv->cond); + + g_hash_table_unref (device->priv->switches); + g_main_context_unref (device->priv->context); + + if (device->priv->handle != NULL) + snd_mixer_close (device->priv->handle); + + G_OBJECT_CLASS (alsa_device_parent_class)->dispose (object); +} + +AlsaDevice * +alsa_device_new (const gchar *name, const gchar *label) +{ + AlsaDevice *device; + gchar *stream_name; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (label != NULL, NULL); + + device = g_object_new (ALSA_TYPE_DEVICE, + "name", name, + "label", label, + "icon", ALSA_DEVICE_ICON, + NULL); + + /* Create input and output streams, they will exist the whole time, but + * the added and removed signals will be emitted when the first control or + * switch is added or the last one removed */ + stream_name = g_strdup_printf ("alsa-input-%s", name); + device->priv->input = alsa_stream_new (stream_name, + MATE_MIXER_DEVICE (device), + MATE_MIXER_STREAM_INPUT); + g_free (stream_name); + + stream_name = g_strdup_printf ("alsa-output-%s", name); + device->priv->output = alsa_stream_new (stream_name, + MATE_MIXER_DEVICE (device), + MATE_MIXER_STREAM_OUTPUT); + g_free (stream_name); + + return device; +} + +gboolean +alsa_device_open (AlsaDevice *device) +{ + snd_mixer_t *handle; + const gchar *name; + gint ret; + + g_return_val_if_fail (ALSA_IS_DEVICE (device), FALSE); + g_return_val_if_fail (device->priv->handle == NULL, FALSE); + + name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + + g_debug ("Opening device %s (%s)", + name, + mate_mixer_device_get_label (MATE_MIXER_DEVICE (device))); + + /* Open the mixer for the current device */ + ret = snd_mixer_open (&handle, 0); + if (ret < 0) { + g_warning ("Failed to open mixer: %s", snd_strerror (ret)); + return FALSE; + } + ret = snd_mixer_attach (handle, name); + if (ret < 0) { + g_warning ("Failed to attach mixer to %s: %s", + name, + snd_strerror (ret)); + + snd_mixer_close (handle); + return FALSE; + } + ret = snd_mixer_selem_register (handle, NULL, NULL); + if (ret < 0) { + g_warning ("Failed to register simple element for %s: %s", + name, + snd_strerror (ret)); + + snd_mixer_close (handle); + return FALSE; + } + ret = snd_mixer_load (handle); + if (ret < 0) { + g_warning ("Failed to load mixer elements for %s: %s", + name, + snd_strerror (ret)); + + snd_mixer_close (handle); + return FALSE; + } + + device->priv->handle = handle; + return TRUE; +} + +void +alsa_device_load (AlsaDevice *device) +{ + GThread *thread; + GError *error = NULL; + snd_mixer_elem_t *el; + + g_return_if_fail (ALSA_IS_DEVICE (device)); + g_return_if_fail (device->priv->handle != NULL); + + /* Process the mixer elements */ + el = snd_mixer_first_elem (device->priv->handle); + while (el != NULL) { + load_element (device, el); + + el = snd_mixer_elem_next (el); + } + + /* Set callback for ALSA events */ + snd_mixer_set_callback (device->priv->handle, handle_callback); + snd_mixer_set_callback_private (device->priv->handle, device); + + /* Start the polling thread */ + thread = g_thread_try_new ("matemixer-alsa-poll", + (GThreadFunc) handle_poll, + device, + &error); + if (thread == NULL) { + /* The error is not treated as fatal, because without the polling + * thread we still have most of the functionality */ + g_warning ("Failed to create poll thread: %s", error->message); + g_error_free (error); + } else + g_thread_unref (thread); +} + +AlsaStream * +alsa_device_get_input_stream (AlsaDevice *device) +{ + g_return_val_if_fail (ALSA_IS_DEVICE (device), NULL); + + /* Normally controlless streams should not exist, here we simulate the + * behaviour for the owning instance */ + if (alsa_stream_is_empty (device->priv->input) == FALSE) + return device->priv->input; + + return NULL; +} + +AlsaStream * +alsa_device_get_output_stream (AlsaDevice *device) +{ + g_return_val_if_fail (ALSA_IS_DEVICE (device), NULL); + + /* Normally controlless streams should not exist, here we simulate the + * behaviour for the owning instance */ + if (alsa_stream_is_empty (device->priv->output) == FALSE) + return device->priv->output; + + return NULL; +} + +static gboolean +add_element (AlsaDevice *device, AlsaStream *stream, AlsaElement *element) +{ + gboolean added = FALSE; + + if (alsa_element_load (element) == FALSE) + return FALSE; + + if (stream != NULL) { + gboolean empty = FALSE; + + if (alsa_stream_is_empty (stream) == TRUE) { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)); + + /* Pretend the stream has just been created now that we are adding + * the first control */ + g_signal_emit_by_name (G_OBJECT (device), + "stream-added", + name); + empty = TRUE; + } + + if (ALSA_IS_STREAM_CONTROL (element)) { + alsa_stream_add_control (stream, ALSA_STREAM_CONTROL (element)); + + /* If this is the first control, set it as the default one. + * The controls often seem to come in the order of importance, but this is + * driver specific, so we may later see if there is another control which + * better matches the default. */ + if (empty == TRUE) + alsa_stream_set_default_control (stream, ALSA_STREAM_CONTROL (element)); + + added = TRUE; + } else if (ALSA_IS_SWITCH (element)) { + /* Switch belonging to a stream */ + alsa_stream_add_switch (stream, ALSA_SWITCH (element)); + added = TRUE; + } + } else if (ALSA_IS_SWITCH (element)) { + /* Switch belonging to the device */ + const gchar *name = + mate_mixer_switch_get_name (MATE_MIXER_SWITCH (element)); + + g_hash_table_insert (device->priv->switches, + g_strdup (name), + g_object_ref (element)); + + g_signal_emit_by_name (G_OBJECT (device), + "switch-added", + name); + added = TRUE; + } + + if G_LIKELY (added == TRUE) { + snd_mixer_elem_t *el = alsa_element_get_snd_element (element); + + snd_mixer_elem_set_callback (el, handle_element_callback); + snd_mixer_elem_set_callback_private (el, device); + } + return added; +} + +static MateMixerSwitch * +alsa_device_get_switch (MateMixerDevice *mmd, const gchar *name) +{ + g_return_val_if_fail (ALSA_IS_DEVICE (mmd), NULL); + g_return_val_if_fail (name != NULL, NULL); + + return g_hash_table_lookup (ALSA_DEVICE (mmd)->priv->switches, name); +} + +static GList * +alsa_device_list_streams (MateMixerDevice *mmd) +{ + AlsaDevice *device; + GList *list = NULL; + + g_return_val_if_fail (ALSA_IS_DEVICE (mmd), NULL); + + device = ALSA_DEVICE (mmd); + + if (device->priv->output != NULL) + list = g_list_prepend (list, g_object_ref (device->priv->output)); + if (device->priv->input != NULL) + list = g_list_prepend (list, g_object_ref (device->priv->input)); + + return list; +} + +static GList * +alsa_device_list_switches (MateMixerDevice *mmd) +{ + GList *list; + + g_return_val_if_fail (ALSA_IS_DEVICE (mmd), NULL); + + /* Convert the hash table to a linked list, this list is expected to be + * cached in the main library */ + list = g_hash_table_get_values (ALSA_DEVICE (mmd)->priv->switches); + if (list != NULL) + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return list; +} + +static gboolean +add_stream_input_control (AlsaDevice *device, snd_mixer_elem_t *el) +{ + AlsaStreamControl *control; + gchar *name; + gchar *label; + MateMixerStreamControlRole role; + + get_control_info (el, &name, &label, &role); + + g_debug ("Found device %s input control %s", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), + label); + + control = alsa_stream_input_control_new (name, label, role); + g_free (name); + g_free (label); + + alsa_element_set_snd_element (ALSA_ELEMENT (control), el); + + if (add_element (device, device->priv->input, ALSA_ELEMENT (control)) == FALSE) { + g_object_unref (control); + return FALSE; + } + return TRUE; +} + +static gboolean +add_stream_output_control (AlsaDevice *device, snd_mixer_elem_t *el) +{ + AlsaStreamControl *control; + gchar *label; + gchar *name; + MateMixerStreamControlRole role; + + get_control_info (el, &name, &label, &role); + + g_debug ("Found device %s output control %s", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), + label); + + control = alsa_stream_output_control_new (name, label, role); + g_free (name); + g_free (label); + + alsa_element_set_snd_element (ALSA_ELEMENT (control), el); + + if (add_element (device, device->priv->output, ALSA_ELEMENT (control)) == FALSE) { + g_object_unref (control); + return FALSE; + } + return TRUE; +} + +static AlsaToggle * +create_toggle (AlsaDevice *device, snd_mixer_elem_t *el, AlsaToggleType type) +{ + AlsaToggle *toggle; + AlsaSwitchOption *on; + AlsaSwitchOption *off; + gchar *name; + gchar *label; + + on = alsa_switch_option_new ("On", _("On"), NULL, 1); + off = alsa_switch_option_new ("Off", _("Off"), NULL, 0); + + get_switch_info (el, &name, &label); + toggle = alsa_toggle_new (name, + label, + type, + on, off); + g_free (name); + g_free (label); + + alsa_element_set_snd_element (ALSA_ELEMENT (toggle), el); + + g_object_unref (on); + g_object_unref (off); + + return toggle; +} + +static gboolean +add_switch (AlsaDevice *device, AlsaStream *stream, snd_mixer_elem_t *el) +{ + AlsaElement *element = NULL; + GList *options = NULL; + gchar *name; + gchar *label; + gchar item[128]; + guint i; + gint count; + gint ret; + + count = snd_mixer_selem_get_enum_items (el); + if G_UNLIKELY (count <= 0) { + g_debug ("Skipping mixer switch %s with %d items", + snd_mixer_selem_get_name (el), + count); + return FALSE; + } + + for (i = 0; i < count; i++) { + ret = snd_mixer_selem_get_enum_item_name (el, i, + sizeof (item), + item); + if G_LIKELY (ret == 0) + options = g_list_prepend (options, + alsa_switch_option_new (item, item, NULL, i)); + else + g_warning ("Failed to read switch item name: %s", snd_strerror (ret)); + } + + get_switch_info (el, &name, &label); + + /* Takes ownership of options */ + element = ALSA_ELEMENT (alsa_switch_new (name, label, g_list_reverse (options))); + g_free (name); + g_free (label); + + alsa_element_set_snd_element (element, el); + + if (add_element (device, stream, element) == FALSE) { + g_object_unref (element); + return FALSE; + } + return TRUE; +} + +static gboolean +add_device_switch (AlsaDevice *device, snd_mixer_elem_t *el) +{ + g_debug ("Reading device %s switch %s (%d items)", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), + snd_mixer_selem_get_name (el), + snd_mixer_selem_get_enum_items (el)); + + return add_switch (device, NULL, el); +} + +static gboolean +add_stream_input_switch (AlsaDevice *device, snd_mixer_elem_t *el) +{ + g_debug ("Reading device %s input switch %s (%d items)", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), + snd_mixer_selem_get_name (el), + snd_mixer_selem_get_enum_items (el)); + + return add_switch (device, device->priv->input, el); +} + +static gboolean +add_stream_output_switch (AlsaDevice *device, snd_mixer_elem_t *el) +{ + g_debug ("Reading device %s output switch %s (%d items)", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), + snd_mixer_selem_get_name (el), + snd_mixer_selem_get_enum_items (el)); + + return add_switch (device, device->priv->output, el); +} + +static gboolean +add_stream_input_toggle (AlsaDevice *device, snd_mixer_elem_t *el) +{ + AlsaToggle *toggle; + + g_debug ("Reading device %s input toggle %s", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), + snd_mixer_selem_get_name (el)); + + toggle = create_toggle (device, el, ALSA_TOGGLE_CAPTURE); + + if (add_element (device, device->priv->input, ALSA_ELEMENT (toggle)) == FALSE) { + g_object_unref (toggle); + return FALSE; + } + return TRUE; +} + +static gboolean +add_stream_output_toggle (AlsaDevice *device, snd_mixer_elem_t *el) +{ + AlsaToggle *toggle; + + g_debug ("Reading device %s output toggle %s", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), + snd_mixer_selem_get_name (el)); + + toggle = create_toggle (device, el, ALSA_TOGGLE_PLAYBACK); + + if (add_element (device, device->priv->output, ALSA_ELEMENT (toggle)) == FALSE) { + g_object_unref (toggle); + return FALSE; + } + return TRUE; +} + +static void +load_element (AlsaDevice *device, snd_mixer_elem_t *el) +{ + gboolean cvolume = FALSE; + gboolean pvolume = FALSE; + + if (snd_mixer_selem_is_enumerated (el) == 1) { + gboolean cenum = FALSE; + gboolean penum = FALSE; + + if (snd_mixer_selem_is_enum_capture (el) == 1) + cenum = TRUE; + if (snd_mixer_selem_is_enum_playback (el) == 1) + penum = TRUE; + + /* Enumerated controls which are not marked as capture or playback + * are considered to be a part of the whole device, although sometimes + * this is incorrectly reported by the driver */ + if (cenum == FALSE && penum == FALSE) { + add_device_switch (device, el); + } + else if (cenum == TRUE) + add_stream_input_switch (device, el); + else if (penum == TRUE) + add_stream_output_switch (device, el); + } + + if (snd_mixer_selem_has_capture_volume (el) == 1 || + snd_mixer_selem_has_common_volume (el) == 1) + cvolume = TRUE; + if (snd_mixer_selem_has_playback_volume (el) == 1 || + snd_mixer_selem_has_common_volume (el) == 1) + pvolume = TRUE; + + if (cvolume == FALSE && pvolume == FALSE) { + /* Control without volume and with a switch are modelled as toggles */ + if (snd_mixer_selem_has_capture_switch (el) == 1) + add_stream_input_toggle (device, el); + + if (snd_mixer_selem_has_playback_switch (el) == 1) + add_stream_output_toggle (device, el); + } else { + if (cvolume == TRUE) + add_stream_input_control (device, el); + if (pvolume == TRUE) + add_stream_output_control (device, el); + } +} + +static void +load_elements_by_name (AlsaDevice *device, const gchar *name) +{ + AlsaElement *element; + + alsa_stream_load_elements (device->priv->input, name); + alsa_stream_load_elements (device->priv->output, name); + + element = g_hash_table_lookup (device->priv->switches, name); + if (element != NULL) + alsa_element_load (element); +} + +static void +remove_elements_by_name (AlsaDevice *device, const gchar *name) +{ + if (alsa_stream_remove_elements (device->priv->input, name) == TRUE) { + /* Removing last stream element "removes" the stream */ + if (alsa_stream_is_empty (device->priv->input) == TRUE) { + const gchar *stream_name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->input)); + + g_signal_emit_by_name (G_OBJECT (device), + "stream-removed", + stream_name); + } + } + + if (alsa_stream_remove_elements (device->priv->output, name) == TRUE) { + /* Removing last stream element "removes" the stream */ + if (alsa_stream_is_empty (device->priv->output) == TRUE) { + const gchar *stream_name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->output)); + + g_signal_emit_by_name (G_OBJECT (device), + "stream-removed", + stream_name); + } + } + + if (g_hash_table_remove (device->priv->switches, name) == TRUE) + g_signal_emit_by_name (G_OBJECT (device), + "switch-removed", + name); +} + +static void +handle_poll (AlsaDevice *device) +{ + /* This function is called in a worker thread. It is supposed to wait for + * ALSA events and call handle_process_events(). Processing the events might + * result in emitting the CLOSED signal and unreffing the instance in the + * owner, so keep an extra reference during the lifetime of the thread. */ + g_object_ref (device); + + while (TRUE) { + gint ret = snd_mixer_wait (device->priv->handle, -1); + if (ret < 0) { + if (ret == EINTR) + continue; + break; + } + + device->priv->events_pending = TRUE; + + /* Process the events in the main thread because most events end up + * emitting signals */ + g_main_context_invoke (device->priv->context, + (GSourceFunc) handle_process_events, + device); + + g_mutex_lock (&device->priv->mutex); + + /* Use a GCond to wait until the events are processed. The processing + * function may be called any time later in the main loop and snd_mixer_wait() + * returns instantly while there are pending events. Without the wait, + * g_main_context_invoke() could be called repeatedly to create idle sources + * until the first idle source function is called. */ + while (device->priv->events_pending == TRUE) + g_cond_wait (&device->priv->cond, &device->priv->mutex); + + g_mutex_unlock (&device->priv->mutex); + + /* Exit the thread if the processing function closed the device */ + if (device->priv->handle == NULL) + break; + } + + g_debug ("Terminating poll thread for device %s", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + + g_object_unref (device); +} + +static gboolean +handle_process_events (AlsaDevice *device) +{ + g_mutex_lock (&device->priv->mutex); + + if (device->priv->handle != NULL) { + gint ret = snd_mixer_handle_events (device->priv->handle); + if (ret < 0) + close_device (device); + } + + device->priv->events_pending = FALSE; + + g_cond_signal (&device->priv->cond); + g_mutex_unlock (&device->priv->mutex); + + return G_SOURCE_REMOVE; +} + +/* ALSA has a per-mixer callback and per-element callback, per-mixer callback + * is only used for added elements and per-element callback for all the + * other messages (no, the documentation doesn't say anything about that). */ +static int +handle_callback (snd_mixer_t *handle, guint mask, snd_mixer_elem_t *el) +{ + if (mask & SND_CTL_EVENT_MASK_ADD) { + AlsaDevice *device = snd_mixer_get_callback_private (handle); + + load_element (device, el); + } + return 0; +} + +static int +handle_element_callback (snd_mixer_elem_t *el, guint mask) +{ + AlsaDevice *device; + gchar *name; + + device = snd_mixer_elem_get_callback_private (el); + name = get_element_name (el); + + if (mask == SND_CTL_EVENT_MASK_REMOVE) { + /* Make sure this function is not called again with the element */ + snd_mixer_elem_set_callback_private (el, NULL); + snd_mixer_elem_set_callback (el, NULL); + + remove_elements_by_name (device, name); + } else { + if (mask & SND_CTL_EVENT_MASK_INFO) { + remove_elements_by_name (device, name); + load_element (device, el); + } + if (mask & SND_CTL_EVENT_MASK_VALUE) + load_elements_by_name (device, name); + } + g_free (name); + + return 0; +} + +static void +close_device (AlsaDevice *device) +{ + if (device->priv->handle != NULL) { + snd_mixer_close (device->priv->handle); + device->priv->handle = NULL; + } + + /* This signal tells the owner that the device has been closed voluntarily + * from within the instance */ + g_signal_emit (G_OBJECT (device), signals[CLOSED], 0); +} + +static gchar * +get_element_name (snd_mixer_elem_t *el) +{ + return g_strdup_printf ("%s-%d", + snd_mixer_selem_get_name (el), + snd_mixer_selem_get_index (el)); +} + +static void +get_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role) +{ + MateMixerStreamControlRole r = MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN; + const gchar *n; + const gchar *l = NULL; + gint i; + + n = snd_mixer_selem_get_name (el); + + for (i = 0; alsa_controls[i].name != NULL; i++) + if (strcmp (n, alsa_controls[i].name) == 0) { + l = alsa_controls[i].label; + r = alsa_controls[i].role; + break; + } + + *name = get_element_name (el); + if (l != NULL) + *label = g_strdup (l); + else + *label = g_strdup (n); + + *role = r; +} + +static void +get_switch_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label) +{ + // MateMixerStreamControlRole r = MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN; + const gchar *n; + const gchar *l = NULL; + // gint i; + + n = snd_mixer_selem_get_name (el); + + // TODO provide translated label and flags + +/* + for (i = 0; alsa_controls[i].name != NULL; i++) + if (strcmp (n, alsa_controls[i].name) == 0) { + l = alsa_controls[i].label; + r = alsa_controls[i].role; + break; + } +*/ + *name = get_element_name (el); + if (l != NULL) + *label = g_strdup (l); + else + *label = g_strdup (n); + + // *role = r; +} diff --git a/backends/alsa/alsa-device.h b/backends/alsa/alsa-device.h new file mode 100644 index 0000000..3b3c970 --- /dev/null +++ b/backends/alsa/alsa-device.h @@ -0,0 +1,74 @@ +/* + * 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/>. + */ + +#ifndef ALSA_DEVICE_H +#define ALSA_DEVICE_H + +#include <glib.h> +#include <glib-object.h> + +#include "alsa-stream.h" + +G_BEGIN_DECLS + +#define ALSA_TYPE_DEVICE \ + (alsa_device_get_type ()) +#define ALSA_DEVICE(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_DEVICE, AlsaDevice)) +#define ALSA_IS_DEVICE(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_DEVICE)) +#define ALSA_DEVICE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), ALSA_TYPE_DEVICE, AlsaDeviceClass)) +#define ALSA_IS_DEVICE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), ALSA_TYPE_DEVICE)) +#define ALSA_DEVICE_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), ALSA_TYPE_DEVICE, AlsaDeviceClass)) + +typedef struct _AlsaDevice AlsaDevice; +typedef struct _AlsaDeviceClass AlsaDeviceClass; +typedef struct _AlsaDevicePrivate AlsaDevicePrivate; + +struct _AlsaDevice +{ + MateMixerDevice parent; + + /*< private >*/ + AlsaDevicePrivate *priv; +}; + +struct _AlsaDeviceClass +{ + MateMixerDeviceClass parent_class; + + /*< private >*/ + void (*closed) (AlsaDevice *device); +}; + +GType alsa_device_get_type (void) G_GNUC_CONST; + +AlsaDevice *alsa_device_new (const gchar *name, + const gchar *label); + +gboolean alsa_device_open (AlsaDevice *device); +void alsa_device_load (AlsaDevice *device); + +AlsaStream *alsa_device_get_input_stream (AlsaDevice *device); +AlsaStream *alsa_device_get_output_stream (AlsaDevice *device); + +G_END_DECLS + +#endif /* ALSA_DEVICE_H */ diff --git a/backends/alsa/alsa-element.c b/backends/alsa/alsa-element.c new file mode 100644 index 0000000..f925064 --- /dev/null +++ b/backends/alsa/alsa-element.c @@ -0,0 +1,53 @@ +/* + * 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 <alsa/asoundlib.h> + +#include "alsa-element.h" + +G_DEFINE_INTERFACE (AlsaElement, alsa_element, G_TYPE_OBJECT) + +static void +alsa_element_default_init (AlsaElementInterface *iface) +{ +} + +snd_mixer_elem_t * +alsa_element_get_snd_element (AlsaElement *element) +{ + g_return_val_if_fail (ALSA_IS_ELEMENT (element), NULL); + + return ALSA_ELEMENT_GET_INTERFACE (element)->get_snd_element (element); +} + +void +alsa_element_set_snd_element (AlsaElement *element, snd_mixer_elem_t *el) +{ + g_return_if_fail (ALSA_IS_ELEMENT (element)); + + ALSA_ELEMENT_GET_INTERFACE (element)->set_snd_element (element, el); +} + +gboolean +alsa_element_load (AlsaElement *element) +{ + g_return_val_if_fail (ALSA_IS_ELEMENT (element), FALSE); + + return ALSA_ELEMENT_GET_INTERFACE (element)->load (element); +} diff --git a/backends/alsa/alsa-element.h b/backends/alsa/alsa-element.h new file mode 100644 index 0000000..01d30f1 --- /dev/null +++ b/backends/alsa/alsa-element.h @@ -0,0 +1,61 @@ +/* + * 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/>. + */ + +#ifndef ALSA_ELEMENT_H +#define ALSA_ELEMENT_H + +#include <glib.h> +#include <glib-object.h> +#include <alsa/asoundlib.h> + +G_BEGIN_DECLS + +#define ALSA_TYPE_ELEMENT \ + (alsa_element_get_type ()) +#define ALSA_ELEMENT(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_ELEMENT, AlsaElement)) +#define ALSA_IS_ELEMENT(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_ELEMENT)) +#define ALSA_ELEMENT_GET_INTERFACE(o) \ + (G_TYPE_INSTANCE_GET_INTERFACE ((o), ALSA_TYPE_ELEMENT, AlsaElementInterface)) + +typedef struct _AlsaElement AlsaElement; /* dummy object */ +typedef struct _AlsaElementInterface AlsaElementInterface; + +struct _AlsaElementInterface +{ + GTypeInterface parent_iface; + + /*< private >*/ + snd_mixer_elem_t *(*get_snd_element) (AlsaElement *element); + void (*set_snd_element) (AlsaElement *element, + snd_mixer_elem_t *el); + + gboolean (*load) (AlsaElement *element); +}; + +GType alsa_element_get_type (void) G_GNUC_CONST; + +snd_mixer_elem_t *alsa_element_get_snd_element (AlsaElement *element); +void alsa_element_set_snd_element (AlsaElement *element, + snd_mixer_elem_t *el); + +gboolean alsa_element_load (AlsaElement *element); + +G_END_DECLS + +#endif /* ALSA_ELEMENT_H */ diff --git a/backends/alsa/alsa-stream-control.c b/backends/alsa/alsa-stream-control.c new file mode 100644 index 0000000..bc7a937 --- /dev/null +++ b/backends/alsa/alsa-stream-control.c @@ -0,0 +1,739 @@ +/* + * 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 <alsa/asoundlib.h> +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "alsa-constants.h" +#include "alsa-element.h" +#include "alsa-stream-control.h" + +struct _AlsaStreamControlPrivate +{ + AlsaControlData data; + guint32 channel_mask; + snd_mixer_elem_t *element; +}; + +static void alsa_element_interface_init (AlsaElementInterface *iface); + +static void alsa_stream_control_class_init (AlsaStreamControlClass *klass); +static void alsa_stream_control_init (AlsaStreamControl *control); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (AlsaStreamControl, alsa_stream_control, + MATE_MIXER_TYPE_STREAM_CONTROL, + G_IMPLEMENT_INTERFACE (ALSA_TYPE_ELEMENT, + alsa_element_interface_init)) + +static snd_mixer_elem_t * alsa_stream_control_get_snd_element (AlsaElement *element); +static void alsa_stream_control_set_snd_element (AlsaElement *element, + snd_mixer_elem_t *el); + +static gboolean alsa_stream_control_load (AlsaElement *element); + +static gboolean alsa_stream_control_set_mute (MateMixerStreamControl *mmsc, + gboolean mute); + +static guint alsa_stream_control_get_num_channels (MateMixerStreamControl *mmsc); + +static guint alsa_stream_control_get_volume (MateMixerStreamControl *mmsc); + +static gboolean alsa_stream_control_set_volume (MateMixerStreamControl *mmsc, + guint volume); + +static gdouble alsa_stream_control_get_decibel (MateMixerStreamControl *mmsc); + +static gboolean alsa_stream_control_set_decibel (MateMixerStreamControl *mmsc, + gdouble decibel); + +static gboolean alsa_stream_control_has_channel_position (MateMixerStreamControl *mmsc, + MateMixerChannelPosition position); +static MateMixerChannelPosition alsa_stream_control_get_channel_position (MateMixerStreamControl *mmsc, + guint channel); + +static guint alsa_stream_control_get_channel_volume (MateMixerStreamControl *mmsc, + guint channel); +static gboolean alsa_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, + guint channel, + guint volume); + +static gdouble alsa_stream_control_get_channel_decibel (MateMixerStreamControl *mmsc, + guint channel); +static gboolean alsa_stream_control_set_channel_decibel (MateMixerStreamControl *mmsc, + guint channel, + gdouble decibel); + +static gboolean alsa_stream_control_set_balance (MateMixerStreamControl *mmsc, + gfloat balance); + +static gboolean alsa_stream_control_set_fade (MateMixerStreamControl *mmsc, + gfloat fade); + +static guint alsa_stream_control_get_min_volume (MateMixerStreamControl *mmsc); +static guint alsa_stream_control_get_max_volume (MateMixerStreamControl *mmsc); +static guint alsa_stream_control_get_normal_volume (MateMixerStreamControl *mmsc); +static guint alsa_stream_control_get_base_volume (MateMixerStreamControl *mmsc); + +static void control_data_get_average_left_right (AlsaControlData *data, + guint *left, + guint *right); +static void control_data_get_average_front_back (AlsaControlData *data, + guint *front, + guint *back); + +static gfloat control_data_get_balance (AlsaControlData *data); +static gfloat control_data_get_fade (AlsaControlData *data); + +static void +alsa_element_interface_init (AlsaElementInterface *iface) +{ + iface->get_snd_element = alsa_stream_control_get_snd_element; + iface->set_snd_element = alsa_stream_control_set_snd_element; + iface->load = alsa_stream_control_load; +} + +static void +alsa_stream_control_class_init (AlsaStreamControlClass *klass) +{ + MateMixerStreamControlClass *control_class; + + control_class = MATE_MIXER_STREAM_CONTROL_CLASS (klass); + + control_class->set_mute = alsa_stream_control_set_mute; + control_class->get_num_channels = alsa_stream_control_get_num_channels; + control_class->get_volume = alsa_stream_control_get_volume; + control_class->set_volume = alsa_stream_control_set_volume; + control_class->get_decibel = alsa_stream_control_get_decibel; + control_class->set_decibel = alsa_stream_control_set_decibel; + control_class->has_channel_position = alsa_stream_control_has_channel_position; + control_class->get_channel_position = alsa_stream_control_get_channel_position; + control_class->get_channel_volume = alsa_stream_control_get_channel_volume; + control_class->set_channel_volume = alsa_stream_control_set_channel_volume; + control_class->get_channel_decibel = alsa_stream_control_get_channel_decibel; + control_class->set_channel_decibel = alsa_stream_control_set_channel_decibel; + control_class->set_balance = alsa_stream_control_set_balance; + control_class->set_fade = alsa_stream_control_set_fade; + control_class->get_min_volume = alsa_stream_control_get_min_volume; + control_class->get_max_volume = alsa_stream_control_get_max_volume; + control_class->get_normal_volume = alsa_stream_control_get_normal_volume; + control_class->get_base_volume = alsa_stream_control_get_base_volume; + + g_type_class_add_private (G_OBJECT_CLASS (klass), sizeof (AlsaStreamControlPrivate)); +} + +static void +alsa_stream_control_init (AlsaStreamControl *control) +{ + control->priv = G_TYPE_INSTANCE_GET_PRIVATE (control, + ALSA_TYPE_STREAM_CONTROL, + AlsaStreamControlPrivate); +} + +AlsaControlData * +alsa_stream_control_get_data (AlsaStreamControl *control) +{ + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), NULL); + + return &control->priv->data; +} + +void +alsa_stream_control_set_data (AlsaStreamControl *control, AlsaControlData *data) +{ + MateMixerStreamControlFlags flags = MATE_MIXER_STREAM_CONTROL_NO_FLAGS; + MateMixerStreamControl *mmsc; + + g_return_if_fail (ALSA_IS_STREAM_CONTROL (control)); + g_return_if_fail (data != NULL); + + mmsc = MATE_MIXER_STREAM_CONTROL (control); + + g_object_freeze_notify (G_OBJECT (control)); + + if (data->channels > 0) { + if (data->switch_usable == TRUE) { + flags |= MATE_MIXER_STREAM_CONTROL_HAS_MUTE; + if (data->active == TRUE) + flags |= MATE_MIXER_STREAM_CONTROL_CAN_SET_MUTE; + } + flags |= MATE_MIXER_STREAM_CONTROL_HAS_VOLUME; + if (data->active == TRUE) + flags |= MATE_MIXER_STREAM_CONTROL_CAN_SET_VOLUME; + } + if (data->max_decibel > -MATE_MIXER_INFINITY) + flags |= MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL; + + control->priv->data = *data; + control->priv->channel_mask = _mate_mixer_create_channel_mask (data->c, data->channels); + + if (data->volume_joined == FALSE) { + if (MATE_MIXER_CHANNEL_MASK_HAS_LEFT (control->priv->channel_mask) && + MATE_MIXER_CHANNEL_MASK_HAS_RIGHT (control->priv->channel_mask)) + flags |= MATE_MIXER_STREAM_CONTROL_CAN_BALANCE; + + if (MATE_MIXER_CHANNEL_MASK_HAS_FRONT (control->priv->channel_mask) && + MATE_MIXER_CHANNEL_MASK_HAS_BACK (control->priv->channel_mask)) + flags |= MATE_MIXER_STREAM_CONTROL_CAN_FADE; + } + + _mate_mixer_stream_control_set_flags (mmsc, flags); + + if (data->switch_usable == TRUE) { + gboolean mute; + + /* If the mute switch is joined, all the channels get the same value, + * otherwise the element has per-channel mute, which we don't support. + * In that case, treat the control as unmuted if any channel is + * unmuted. */ + if (data->channels == 1 || data->switch_joined == TRUE) { + mute = data->m[0]; + } else { + gint i; + mute = TRUE; + for (i = 0; i < data->channels; i++) + if (data->m[i] == FALSE) { + mute = FALSE; + break; + } + } + _mate_mixer_stream_control_set_mute (mmsc, mute); + } else + _mate_mixer_stream_control_set_mute (mmsc, FALSE); + + if (flags & MATE_MIXER_STREAM_CONTROL_CAN_BALANCE) + _mate_mixer_stream_control_set_balance (mmsc, control_data_get_balance (data)); + if (flags & MATE_MIXER_STREAM_CONTROL_CAN_FADE) + _mate_mixer_stream_control_set_fade (mmsc, control_data_get_fade (data)); + + g_object_thaw_notify (G_OBJECT (control)); +} + +static snd_mixer_elem_t * +alsa_stream_control_get_snd_element (AlsaElement *element) +{ + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (element), NULL); + + return ALSA_STREAM_CONTROL (element)->priv->element; +} + +static void +alsa_stream_control_set_snd_element (AlsaElement *element, snd_mixer_elem_t *el) +{ + g_return_if_fail (ALSA_IS_STREAM_CONTROL (element)); + g_return_if_fail (el != NULL); + + ALSA_STREAM_CONTROL (element)->priv->element = el; +} + +static gboolean +alsa_stream_control_load (AlsaElement *element) +{ + AlsaStreamControl *control; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (element), FALSE); + + control = ALSA_STREAM_CONTROL (element); + + return ALSA_STREAM_CONTROL_GET_CLASS (control)->load (control); +} + +static gboolean +alsa_stream_control_set_mute (MateMixerStreamControl *mmsc, gboolean mute) +{ + AlsaStreamControl *control; + gboolean change = FALSE; + gint i; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), FALSE); + + control = ALSA_STREAM_CONTROL (mmsc); + + /* If the switch is joined, only verify the first channel */ + if (control->priv->data.switch_joined == TRUE) { + if (control->priv->data.m[0] != mute) + change = TRUE; + } else { + /* Avoid trying to set the mute if all channels are already at the + * selected mute value */ + for (i = 0; i < control->priv->data.channels; i++) + if (control->priv->data.m[i] != mute) { + change = TRUE; + break; + } + } + + if (change == TRUE) { + AlsaStreamControlClass *klass; + + klass = ALSA_STREAM_CONTROL_GET_CLASS (control); + if (klass->set_mute (control, mute) == FALSE) + return FALSE; + + for (i = 0; i < control->priv->data.channels; i++) + control->priv->data.m[i] = mute; + } + return TRUE; +} + +static guint +alsa_stream_control_get_num_channels (MateMixerStreamControl *mmsc) +{ + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), 0); + + return ALSA_STREAM_CONTROL (mmsc)->priv->data.channels; +} + +static guint +alsa_stream_control_get_volume (MateMixerStreamControl *mmsc) +{ + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), 0); + + return ALSA_STREAM_CONTROL (mmsc)->priv->data.volume; +} + +static gboolean +alsa_stream_control_set_volume (MateMixerStreamControl *mmsc, guint volume) +{ + AlsaStreamControl *control; + gboolean change = FALSE; + gint i; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), FALSE); + + control = ALSA_STREAM_CONTROL (mmsc); + volume = CLAMP (volume, control->priv->data.min, control->priv->data.max); + + /* If the volume is joined, only verify the first channel */ + if (control->priv->data.volume_joined == TRUE) { + if (control->priv->data.v[0] != volume) + change = TRUE; + } else { + /* Avoid trying to set the volume if all channels are already at the + * selected volume */ + for (i = 0; i < control->priv->data.channels; i++) + if (control->priv->data.v[i] != volume) { + change = TRUE; + break; + } + } + + if (change == TRUE) { + AlsaStreamControlClass *klass; + + klass = ALSA_STREAM_CONTROL_GET_CLASS (control); + if (klass->set_volume (control, volume) == FALSE) + return FALSE; + + for (i = 0; i < control->priv->data.channels; i++) + control->priv->data.v[i] = volume; + + g_object_notify (G_OBJECT (control), "volume"); + } + return TRUE; +} + +static gdouble +alsa_stream_control_get_decibel (MateMixerStreamControl *mmsc) +{ + AlsaStreamControl *control; + AlsaStreamControlClass *klass; + guint volume; + gdouble decibel; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), -MATE_MIXER_INFINITY); + + control = ALSA_STREAM_CONTROL (mmsc); + klass = ALSA_STREAM_CONTROL_GET_CLASS (control); + volume = alsa_stream_control_get_volume (mmsc); + + if (klass->get_decibel_from_volume (control, volume, &decibel) == FALSE) + return FALSE; + + return decibel; +} + +static gboolean +alsa_stream_control_set_decibel (MateMixerStreamControl *mmsc, gdouble decibel) +{ + AlsaStreamControl *control; + AlsaStreamControlClass *klass; + guint volume; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), FALSE); + + control = ALSA_STREAM_CONTROL (mmsc); + klass = ALSA_STREAM_CONTROL_GET_CLASS (control); + + if (klass->get_volume_from_decibel (control, decibel, &volume) == FALSE) + return FALSE; + + return alsa_stream_control_set_volume (mmsc, volume); +} + +static gboolean +alsa_stream_control_has_channel_position (MateMixerStreamControl *mmsc, + MateMixerChannelPosition position) +{ + AlsaStreamControl *control; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), FALSE); + + control = ALSA_STREAM_CONTROL (mmsc); + + if MATE_MIXER_CHANNEL_MASK_HAS_CHANNEL (control->priv->channel_mask, position) + return TRUE; + else + return FALSE; +} + +static MateMixerChannelPosition +alsa_stream_control_get_channel_position (MateMixerStreamControl *mmsc, guint channel) +{ + AlsaStreamControl *control; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), MATE_MIXER_CHANNEL_UNKNOWN); + + control = ALSA_STREAM_CONTROL (mmsc); + + if (channel >= control->priv->data.channels) + return MATE_MIXER_CHANNEL_UNKNOWN; + + return control->priv->data.c[channel]; +} + +static guint +alsa_stream_control_get_channel_volume (MateMixerStreamControl *mmsc, guint channel) +{ + AlsaStreamControl *control; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), 0); + + control = ALSA_STREAM_CONTROL (mmsc); + + if (channel >= control->priv->data.channels) + return FALSE; + + return control->priv->data.v[channel]; +} + +static gboolean +alsa_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, guint channel, guint volume) +{ + AlsaStreamControl *control; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), FALSE); + + control = ALSA_STREAM_CONTROL (mmsc); + + if (channel >= control->priv->data.channels) + return FALSE; + + /* Set volume for all channels at once when channels are joined */ + if (control->priv->data.volume_joined == TRUE) + return alsa_stream_control_set_volume (mmsc, volume); + + if (volume != control->priv->data.v[channel]) { + AlsaStreamControlClass *klass; + + /* Convert channel index to ALSA channel position and make sure it is valid */ + snd_mixer_selem_channel_id_t c = alsa_channel_map_to[control->priv->data.c[channel]]; + if G_UNLIKELY (c == SND_MIXER_SCHN_UNKNOWN) { + g_warn_if_reached (); + return FALSE; + } + + klass = ALSA_STREAM_CONTROL_GET_CLASS (control); + if (klass->set_channel_volume (control, c, volume) == FALSE) + return FALSE; + + control->priv->data.v[channel] = volume; + + g_object_notify (G_OBJECT (control), "volume"); + } + return TRUE; +} + +static gdouble +alsa_stream_control_get_channel_decibel (MateMixerStreamControl *mmsc, guint channel) +{ + AlsaStreamControl *control; + AlsaStreamControlClass *klass; + guint volume; + gdouble decibel; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), -MATE_MIXER_INFINITY); + + control = ALSA_STREAM_CONTROL (mmsc); + + if (channel >= control->priv->data.channels) + return FALSE; + + klass = ALSA_STREAM_CONTROL_GET_CLASS (control); + volume = control->priv->data.v[channel]; + + if (klass->get_decibel_from_volume (control, volume, &decibel) == FALSE) + return FALSE; + + return decibel; +} + +static gboolean +alsa_stream_control_set_channel_decibel (MateMixerStreamControl *mmsc, + guint channel, + gdouble decibel) +{ + AlsaStreamControl *control; + AlsaStreamControlClass *klass; + guint volume; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), FALSE); + + control = ALSA_STREAM_CONTROL (mmsc); + klass = ALSA_STREAM_CONTROL_GET_CLASS (control); + + if (klass->get_volume_from_decibel (control, decibel, &volume) == FALSE) + return FALSE; + + return alsa_stream_control_set_channel_volume (mmsc, channel, volume); +} + +static gboolean +alsa_stream_control_set_balance (MateMixerStreamControl *mmsc, gfloat balance) +{ + AlsaStreamControlClass *klass; + AlsaStreamControl *control; + AlsaControlData *data; + guint left, + right; + guint nleft, + nright; + guint max; + guint channel; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), FALSE); + + control = ALSA_STREAM_CONTROL (mmsc); + klass = ALSA_STREAM_CONTROL_GET_CLASS (control); + + data = &control->priv->data; + control_data_get_average_left_right (data, &left, &right); + + max = MAX (left, right); + if (balance <= 0) { + nright = (balance + 1.0f) * max; + nleft = max; + } else { + nleft = (1.0f - balance) * max; + nright = max; + } + + for (channel = 0; channel < data->channels; channel++) { + gboolean lc = MATE_MIXER_IS_LEFT_CHANNEL (data->c[channel]); + gboolean rc = MATE_MIXER_IS_RIGHT_CHANNEL (data->c[channel]); + + if (lc == TRUE || rc == TRUE) { + guint volume; + if (lc == TRUE) { + if (left == 0) + volume = nleft; + else + volume = CLAMP (((guint64) data->v[channel] * (guint64) nleft) / (guint64) left, + data->min, + data->max); + } else { + if (right == 0) + volume = nright; + else + volume = CLAMP (((guint64) data->v[channel] * (guint64) nright) / (guint64) right, + data->min, + data->max); + } + + if (klass->set_channel_volume (control, + alsa_channel_map_to[data->c[channel]], + volume) == TRUE) + data->v[channel] = volume; + } + } + return TRUE; +} + +static gboolean +alsa_stream_control_set_fade (MateMixerStreamControl *mmsc, gfloat fade) +{ + AlsaStreamControlClass *klass; + AlsaStreamControl *control; + AlsaControlData *data; + guint front, + back; + guint nfront, + nback; + guint max; + guint channel; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (mmsc), FALSE); + + control = ALSA_STREAM_CONTROL (mmsc); + klass = ALSA_STREAM_CONTROL_GET_CLASS (control); + + data = &control->priv->data; + control_data_get_average_front_back (data, &front, &back); + + max = MAX (front, back); + if (fade <= 0) { + nback = (fade + 1.0f) * max; + nfront = max; + } else { + nfront = (1.0f - fade) * max; + nback = max; + } + + for (channel = 0; channel < data->channels; channel++) { + gboolean fc = MATE_MIXER_IS_FRONT_CHANNEL (data->c[channel]); + gboolean bc = MATE_MIXER_IS_BACK_CHANNEL (data->c[channel]); + + if (fc == TRUE || bc == TRUE) { + guint volume; + if (fc == TRUE) { + if (front == 0) + volume = nfront; + else + volume = CLAMP (((guint64) data->v[channel] * (guint64) nfront) / (guint64) front, + data->min, + data->max); + } else { + if (back == 0) + volume = nback; + else + volume = CLAMP (((guint64) data->v[channel] * (guint64) nback) / (guint64) back, + data->min, + data->max); + } + + if (klass->set_channel_volume (control, + alsa_channel_map_to[data->c[channel]], + volume) == TRUE) + data->v[channel] = volume; + } + } + return TRUE; +} + +static guint +alsa_stream_control_get_min_volume (MateMixerStreamControl *msc) +{ + return ALSA_STREAM_CONTROL (msc)->priv->data.min; +} + +static guint +alsa_stream_control_get_max_volume (MateMixerStreamControl *msc) +{ + return ALSA_STREAM_CONTROL (msc)->priv->data.max; +} + +static guint +alsa_stream_control_get_normal_volume (MateMixerStreamControl *msc) +{ + return ALSA_STREAM_CONTROL (msc)->priv->data.max; +} + +static guint +alsa_stream_control_get_base_volume (MateMixerStreamControl *msc) +{ + return ALSA_STREAM_CONTROL (msc)->priv->data.max; +} + +static void +control_data_get_average_left_right (AlsaControlData *data, guint *left, guint *right) +{ + guint l = 0, + r = 0; + guint nl = 0, + nr = 0; + guint channel; + + for (channel = 0; channel < data->channels; channel++) + if MATE_MIXER_IS_LEFT_CHANNEL (data->c[channel]) { + l += data->v[channel]; + nl++; + } + else if MATE_MIXER_IS_RIGHT_CHANNEL (data->c[channel]) { + r += data->v[channel]; + nr++; + } + + *left = (nl > 0) ? l / nl : data->max; + *right = (nr > 0) ? r / nr : data->max; +} + +static void +control_data_get_average_front_back (AlsaControlData *data, guint *front, guint *back) +{ + guint f = 0, + b = 0; + guint nf = 0, + nb = 0; + guint channel; + + for (channel = 0; channel < data->channels; channel++) + if MATE_MIXER_IS_FRONT_CHANNEL (data->c[channel]) { + f += data->v[channel]; + nf++; + } + else if MATE_MIXER_IS_RIGHT_CHANNEL (data->c[channel]) { + b += data->v[channel]; + nb++; + } + + *front = (nf > 0) ? f / nf : data->max; + *back = (nb > 0) ? b / nb : data->max; +} + +static gfloat +control_data_get_balance (AlsaControlData *data) +{ + guint left; + guint right; + + control_data_get_average_left_right (data, &left, &right); + if (left == right) + return 0.0f; + + if (left > right) + return -1.0f + ((gfloat) right / (gfloat) left); + else + return +1.0f - ((gfloat) left / (gfloat) right); +} + +static gfloat +control_data_get_fade (AlsaControlData *data) +{ + guint front; + guint back; + + control_data_get_average_front_back (data, &front, &back); + if (front == back) + return 0.0f; + + if (front > back) + return -1.0f + ((gfloat) back / (gfloat) front); + else + return +1.0f - ((gfloat) front / (gfloat) back); +} diff --git a/backends/alsa/alsa-stream-control.h b/backends/alsa/alsa-stream-control.h new file mode 100644 index 0000000..f9ac6b6 --- /dev/null +++ b/backends/alsa/alsa-stream-control.h @@ -0,0 +1,111 @@ +/* + * 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/>. + */ + +#ifndef ALSA_STREAM_CONTROL_H +#define ALSA_STREAM_CONTROL_H + +#include <glib.h> +#include <glib-object.h> +#include <alsa/asoundlib.h> +#include <libmatemixer/matemixer.h> + +G_BEGIN_DECLS + +typedef struct { + gboolean active; + MateMixerChannelPosition c[MATE_MIXER_CHANNEL_MAX]; + guint v[MATE_MIXER_CHANNEL_MAX]; + gboolean m[MATE_MIXER_CHANNEL_MAX]; + guint volume; + gboolean volume_joined; + gboolean switch_usable; + gboolean switch_joined; + guint min; + guint max; + gdouble min_decibel; + gdouble max_decibel; + guint channels; +} AlsaControlData; + +extern const MateMixerChannelPosition alsa_channel_map_from[SND_MIXER_SCHN_LAST]; +extern const snd_mixer_selem_channel_id_t alsa_channel_map_to[MATE_MIXER_CHANNEL_MAX]; + +#define ALSA_TYPE_STREAM_CONTROL \ + (alsa_stream_control_get_type ()) +#define ALSA_STREAM_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_STREAM_CONTROL, AlsaStreamControl)) +#define ALSA_IS_STREAM_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_STREAM_CONTROL)) +#define ALSA_STREAM_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), ALSA_TYPE_STREAM_CONTROL, AlsaStreamControlClass)) +#define ALSA_IS_STREAM_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), ALSA_TYPE_STREAM_CONTROL)) +#define ALSA_STREAM_CONTROL_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), ALSA_TYPE_STREAM_CONTROL, AlsaStreamControlClass)) + +typedef struct _AlsaStreamControl AlsaStreamControl; +typedef struct _AlsaStreamControlClass AlsaStreamControlClass; +typedef struct _AlsaStreamControlPrivate AlsaStreamControlPrivate; + +struct _AlsaStreamControl +{ + MateMixerStreamControl parent; + + /*< private >*/ + AlsaStreamControlPrivate *priv; + +}; + +struct _AlsaStreamControlClass +{ + MateMixerStreamControlClass parent_class; + + /*< private >*/ + gboolean (*load) (AlsaStreamControl *control); + + gboolean (*set_mute) (AlsaStreamControl *control, + gboolean mute); + + gboolean (*set_volume) (AlsaStreamControl *control, + guint volume); + + gboolean (*set_channel_volume) (AlsaStreamControl *control, + snd_mixer_selem_channel_id_t channel, + guint volume); + + gboolean (*get_volume_from_decibel) (AlsaStreamControl *control, + gdouble decibel, + guint *volume); + + gboolean (*get_decibel_from_volume) (AlsaStreamControl *control, + guint volume, + gdouble *decibel); +}; + +GType alsa_stream_control_get_type (void) G_GNUC_CONST; + +AlsaControlData * alsa_stream_control_get_data (AlsaStreamControl *control); + +void alsa_stream_control_set_data (AlsaStreamControl *control, + AlsaControlData *data); + +gboolean alsa_stream_control_set_role (AlsaStreamControl *control, + MateMixerStreamControlRole role); + +G_END_DECLS + +#endif /* ALSA_STREAM_CONTROL_H */ diff --git a/backends/alsa/alsa-stream-input-control.c b/backends/alsa/alsa-stream-input-control.c new file mode 100644 index 0000000..2ef0c42 --- /dev/null +++ b/backends/alsa/alsa-stream-input-control.c @@ -0,0 +1,329 @@ +/* + * 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 <alsa/asoundlib.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "alsa-element.h" +#include "alsa-stream-control.h" +#include "alsa-stream-input-control.h" + +static void alsa_stream_input_control_class_init (AlsaStreamInputControlClass *klass); +static void alsa_stream_input_control_init (AlsaStreamInputControl *control); + +G_DEFINE_TYPE (AlsaStreamInputControl, alsa_stream_input_control, ALSA_TYPE_STREAM_CONTROL) + +static gboolean alsa_stream_input_control_load (AlsaStreamControl *control); + +static gboolean alsa_stream_input_control_set_mute (AlsaStreamControl *control, + gboolean mute); + +static gboolean alsa_stream_input_control_set_volume (AlsaStreamControl *control, + guint volume); + +static gboolean alsa_stream_input_control_set_channel_volume (AlsaStreamControl *control, + snd_mixer_selem_channel_id_t channel, + guint volume); + +static gboolean alsa_stream_input_control_get_volume_from_decibel (AlsaStreamControl *control, + gdouble decibel, + guint *volume); + +static gboolean alsa_stream_input_control_get_decibel_from_volume (AlsaStreamControl *control, + guint volume, + gdouble *decibel); + +static void read_volume_data (snd_mixer_elem_t *el, + AlsaControlData *data); + +static void +alsa_stream_input_control_class_init (AlsaStreamInputControlClass *klass) +{ + AlsaStreamControlClass *control_class; + + control_class = ALSA_STREAM_CONTROL_CLASS (klass); + + control_class->load = alsa_stream_input_control_load; + control_class->set_mute = alsa_stream_input_control_set_mute; + control_class->set_volume = alsa_stream_input_control_set_volume; + control_class->set_channel_volume = alsa_stream_input_control_set_channel_volume; + control_class->get_volume_from_decibel = alsa_stream_input_control_get_volume_from_decibel; + control_class->get_decibel_from_volume = alsa_stream_input_control_get_decibel_from_volume; +} + +static void +alsa_stream_input_control_init (AlsaStreamInputControl *control) +{ +} + +AlsaStreamControl * +alsa_stream_input_control_new (const gchar *name, + const gchar *label, + MateMixerStreamControlRole role) +{ + return g_object_new (ALSA_TYPE_STREAM_INPUT_CONTROL, + "name", name, + "label", label, + "role", role, + NULL); +} + +static gboolean +alsa_stream_input_control_load (AlsaStreamControl *control) +{ + AlsaControlData data; + snd_mixer_elem_t *el; + + g_return_val_if_fail (ALSA_IS_STREAM_INPUT_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + /* Expect that the element has a volume control */ + if G_UNLIKELY (snd_mixer_selem_has_capture_volume (el) == 0 && + snd_mixer_selem_has_common_volume (el) == 0) { + g_warn_if_reached (); + return FALSE; + } + + memset (&data, 0, sizeof (AlsaControlData)); + + /* We model any control switch as mute */ + if (snd_mixer_selem_has_capture_switch (el) == 1 || + snd_mixer_selem_has_common_switch (el) == 1) + data.switch_usable = TRUE; + + data.active = snd_mixer_selem_is_active (el); + + /* Read the volume data but do not error out if it fails, since ALSA reports + * the control to have a volume, expect the control to match what we need - slider + * with an optional mute toggle. + * If it fails to read the volume data, just treat it as a volumeless control */ + read_volume_data (el, &data); + + alsa_stream_control_set_data (control, &data); + return TRUE; +} + +static gboolean +alsa_stream_input_control_set_mute (AlsaStreamControl *control, gboolean mute) +{ + snd_mixer_elem_t *el; + gint ret; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + /* Set the switch for all channels */ + ret = snd_mixer_selem_set_capture_switch_all (el, !mute); + if (ret < 0) { + g_warning ("Failed to set capture switch: %s", snd_strerror (ret)); + return FALSE; + } + return TRUE; +} + +static gboolean +alsa_stream_input_control_set_volume (AlsaStreamControl *control, guint volume) +{ + snd_mixer_elem_t *el; + gint ret; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + /* Set the volume for all channels */ + ret = snd_mixer_selem_set_capture_volume_all (el, volume); + if (ret < 0) { + g_warning ("Failed to set volume: %s", snd_strerror (ret)); + return FALSE; + } + return TRUE; +} + +static gboolean +alsa_stream_input_control_set_channel_volume (AlsaStreamControl *control, + snd_mixer_selem_channel_id_t channel, + guint volume) +{ + snd_mixer_elem_t *el; + gint ret; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + /* Set the volume for a single channels, the volume may still be "joined" and + * set all the channels by itself */ + ret = snd_mixer_selem_set_capture_volume (el, channel, volume); + if (ret < 0) { + g_warning ("Failed to set channel volume: %s", snd_strerror (ret)); + return FALSE; + } + return TRUE; +} + +static gboolean +alsa_stream_input_control_get_volume_from_decibel (AlsaStreamControl *control, + gdouble decibel, + guint *volume) +{ + snd_mixer_elem_t *el; + glong value; + gint ret; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + ret = snd_mixer_selem_ask_capture_dB_vol (el, (glong) (decibel * 100), 0, &value); + if (ret < 0) { + g_warning ("Failed to convert volume: %s", snd_strerror (ret)); + return FALSE; + } + + *volume = value; + return TRUE; +} + +static gboolean +alsa_stream_input_control_get_decibel_from_volume (AlsaStreamControl *control, + guint volume, + gdouble *decibel) +{ + snd_mixer_elem_t *el; + glong value; + gint ret; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + ret = snd_mixer_selem_ask_capture_vol_dB (el, (glong) volume, &value); + if (ret < 0) { + g_warning ("Failed to convert volume: %s", snd_strerror (ret)); + return FALSE; + } + + *decibel = value / 100.0; + return TRUE; +} + +static void +read_volume_data (snd_mixer_elem_t *el, AlsaControlData *data) +{ + glong volume; + glong min, max; + gint ret; + gint i; + + /* Read volume ranges, this call should never fail on valid input */ + ret = snd_mixer_selem_get_capture_volume_range (el, &min, &max); + if G_UNLIKELY (ret < 0) { + g_warning ("Failed to read capture volume range: %s", snd_strerror (ret)); + return; + } + data->min = (guint) min; + data->max = (guint) max; + + /* This fails when decibels are not supported */ + ret = snd_mixer_selem_get_capture_dB_range (el, &min, &max); + if (ret == 0) { + data->min_decibel = min / 100.0; + data->max_decibel = max / 100.0; + } else + data->min_decibel = data->max_decibel = -MATE_MIXER_INFINITY; + + for (i = 0; i < MATE_MIXER_CHANNEL_MAX; i++) + data->v[i] = data->min; + + data->volume = data->min; + data->volume_joined = snd_mixer_selem_has_capture_volume_joined (el); + + if (data->switch_usable == TRUE) + data->switch_joined = snd_mixer_selem_has_capture_switch_joined (el); + + if (snd_mixer_selem_is_capture_mono (el) == 1) { + /* Special handling for single channel controls */ + ret = snd_mixer_selem_get_capture_volume (el, SND_MIXER_SCHN_MONO, &volume); + if (ret == 0) { + data->channels = 1; + + data->c[0] = MATE_MIXER_CHANNEL_MONO; + data->v[0] = data->volume = (guint) volume; + } else { + g_warning ("Failed to read capture volume: %s", snd_strerror (ret)); + } + + if (data->switch_usable == TRUE) { + gint value; + + ret = snd_mixer_selem_get_capture_switch (el, SND_MIXER_SCHN_MONO, &value); + if G_LIKELY (ret == 0) + data->m[0] = !value; + } + } else { + snd_mixer_selem_channel_id_t channel; + + /* We use numeric channel indices, but ALSA only works with channel + * positions, go over all the positions supported by ALSA and create + * a list of channels */ + for (channel = 0; channel < SND_MIXER_SCHN_LAST; channel++) { + if (snd_mixer_selem_has_capture_channel (el, channel) == 0) + continue; + + if (data->switch_usable == TRUE) { + gint value; + + ret = snd_mixer_selem_get_capture_switch (el, channel, &value); + if (ret == 0) + data->m[channel] = !value; + } + + ret = snd_mixer_selem_get_capture_volume (el, channel, &volume); + if (ret < 0) { + g_warning ("Failed to read capture volume: %s", snd_strerror (ret)); + continue; + } + data->channels++; + + /* The single value volume is the highest channel volume */ + if (data->volume < volume) + data->volume = volume; + + data->c[channel] = alsa_channel_map_from[channel]; + data->v[channel] = (guint) volume; + } + } +} diff --git a/backends/alsa/alsa-stream-input-control.h b/backends/alsa/alsa-stream-input-control.h new file mode 100644 index 0000000..c427e3c --- /dev/null +++ b/backends/alsa/alsa-stream-input-control.h @@ -0,0 +1,64 @@ +/* + * 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/>. + */ + +#ifndef ALSA_STREAM_INPUT_CONTROL_H +#define ALSA_STREAM_INPUT_CONTROL_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "alsa-stream-control.h" + +G_BEGIN_DECLS + +#define ALSA_TYPE_STREAM_INPUT_CONTROL \ + (alsa_stream_input_control_get_type ()) +#define ALSA_STREAM_INPUT_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_STREAM_INPUT_CONTROL, AlsaStreamInputControl)) +#define ALSA_IS_STREAM_INPUT_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_STREAM_INPUT_CONTROL)) +#define ALSA_STREAM_INPUT_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), ALSA_TYPE_STREAM_INPUT_CONTROL, AlsaStreamInputControlClass)) +#define ALSA_IS_STREAM_INPUT_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), ALSA_TYPE_STREAM_INPUT_CONTROL)) +#define ALSA_STREAM_INPUT_CONTROL_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), ALSA_TYPE_STREAM_INPUT_CONTROL, AlsaStreamInputControlClass)) + +typedef struct _AlsaStreamInputControl AlsaStreamInputControl; +typedef struct _AlsaStreamInputControlClass AlsaStreamInputControlClass; +typedef struct _AlsaStreamInputControlPrivate AlsaStreamInputControlPrivate; + +struct _AlsaStreamInputControl +{ + AlsaStreamControl parent; +}; + +struct _AlsaStreamInputControlClass +{ + AlsaStreamControlClass parent_class; +}; + +GType alsa_stream_input_control_get_type (void) G_GNUC_CONST; + +AlsaStreamControl *alsa_stream_input_control_new (const gchar *name, + const gchar *label, + MateMixerStreamControlRole role); + +G_END_DECLS + +#endif /* ALSA_STREAM_INPUT_CONTROL_H */ diff --git a/backends/alsa/alsa-stream-output-control.c b/backends/alsa/alsa-stream-output-control.c new file mode 100644 index 0000000..5a3e6b3 --- /dev/null +++ b/backends/alsa/alsa-stream-output-control.c @@ -0,0 +1,329 @@ +/* + * 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 <alsa/asoundlib.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "alsa-element.h" +#include "alsa-stream-control.h" +#include "alsa-stream-output-control.h" + +static void alsa_stream_output_control_class_init (AlsaStreamOutputControlClass *klass); +static void alsa_stream_output_control_init (AlsaStreamOutputControl *control); + +G_DEFINE_TYPE (AlsaStreamOutputControl, alsa_stream_output_control, ALSA_TYPE_STREAM_CONTROL) + +static gboolean alsa_stream_output_control_load (AlsaStreamControl *control); + +static gboolean alsa_stream_output_control_set_mute (AlsaStreamControl *control, + gboolean mute); + +static gboolean alsa_stream_output_control_set_volume (AlsaStreamControl *control, + guint volume); + +static gboolean alsa_stream_output_control_set_channel_volume (AlsaStreamControl *control, + snd_mixer_selem_channel_id_t channel, + guint volume); + +static gboolean alsa_stream_output_control_get_volume_from_decibel (AlsaStreamControl *control, + gdouble decibel, + guint *volume); + +static gboolean alsa_stream_output_control_get_decibel_from_volume (AlsaStreamControl *control, + guint volume, + gdouble *decibel); + +static void read_volume_data (snd_mixer_elem_t *el, + AlsaControlData *data); + +static void +alsa_stream_output_control_class_init (AlsaStreamOutputControlClass *klass) +{ + AlsaStreamControlClass *control_class; + + control_class = ALSA_STREAM_CONTROL_CLASS (klass); + + control_class->load = alsa_stream_output_control_load; + control_class->set_mute = alsa_stream_output_control_set_mute; + control_class->set_volume = alsa_stream_output_control_set_volume; + control_class->set_channel_volume = alsa_stream_output_control_set_channel_volume; + control_class->get_volume_from_decibel = alsa_stream_output_control_get_volume_from_decibel; + control_class->get_decibel_from_volume = alsa_stream_output_control_get_decibel_from_volume; +} + +static void +alsa_stream_output_control_init (AlsaStreamOutputControl *control) +{ +} + +AlsaStreamControl * +alsa_stream_output_control_new (const gchar *name, + const gchar *label, + MateMixerStreamControlRole role) +{ + return g_object_new (ALSA_TYPE_STREAM_OUTPUT_CONTROL, + "name", name, + "label", label, + "role", role, + NULL); +} + +static gboolean +alsa_stream_output_control_load (AlsaStreamControl *control) +{ + AlsaControlData data; + snd_mixer_elem_t *el; + + g_return_val_if_fail (ALSA_IS_STREAM_OUTPUT_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + /* Expect that the element has a volume control */ + if G_UNLIKELY (snd_mixer_selem_has_playback_volume (el) == 0 && + snd_mixer_selem_has_common_volume (el) == 0) { + g_warn_if_reached (); + return FALSE; + } + + memset (&data, 0, sizeof (AlsaControlData)); + + /* We model any control switch as mute */ + if (snd_mixer_selem_has_playback_switch (el) == 1 || + snd_mixer_selem_has_common_switch (el) == 1) + data.switch_usable = TRUE; + + data.active = snd_mixer_selem_is_active (el); + + /* Read the volume data but do not error out if it fails, since ALSA reports + * the control to have a volume, expect the control to match what we need - slider + * with an optional mute toggle. + * If it fails to read the volume data, just treat it as a volumeless control */ + read_volume_data (el, &data); + + alsa_stream_control_set_data (control, &data); + return TRUE; +} + +static gboolean +alsa_stream_output_control_set_mute (AlsaStreamControl *control, gboolean mute) +{ + snd_mixer_elem_t *el; + gint ret; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + /* Set the switch for all channels */ + ret = snd_mixer_selem_set_playback_switch_all (el, !mute); + if (ret < 0) { + g_warning ("Failed to set playback switch: %s", snd_strerror (ret)); + return FALSE; + } + return TRUE; +} + +static gboolean +alsa_stream_output_control_set_volume (AlsaStreamControl *control, guint volume) +{ + snd_mixer_elem_t *el; + gint ret; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + /* Set the volume for all channels */ + ret = snd_mixer_selem_set_playback_volume_all (el, volume); + if (ret < 0) { + g_warning ("Failed to set volume: %s", snd_strerror (ret)); + return FALSE; + } + return TRUE; +} + +static gboolean +alsa_stream_output_control_set_channel_volume (AlsaStreamControl *control, + snd_mixer_selem_channel_id_t channel, + guint volume) +{ + snd_mixer_elem_t *el; + gint ret; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + /* Set the volume for a single channels, the volume may still be "joined" and + * set all the channels by itself */ + ret = snd_mixer_selem_set_playback_volume (el, channel, volume); + if (ret < 0) { + g_warning ("Failed to set channel volume: %s", snd_strerror (ret)); + return FALSE; + } + return TRUE; +} + +static gboolean +alsa_stream_output_control_get_volume_from_decibel (AlsaStreamControl *control, + gdouble decibel, + guint *volume) +{ + snd_mixer_elem_t *el; + glong value; + gint ret; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + ret = snd_mixer_selem_ask_playback_dB_vol (el, (glong) (decibel * 100), 0, &value); + if (ret < 0) { + g_warning ("Failed to convert volume: %s", snd_strerror (ret)); + return FALSE; + } + + *volume = value; + return TRUE; +} + +static gboolean +alsa_stream_output_control_get_decibel_from_volume (AlsaStreamControl *control, + guint volume, + gdouble *decibel) +{ + snd_mixer_elem_t *el; + glong value; + gint ret; + + g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + ret = snd_mixer_selem_ask_playback_vol_dB (el, (glong) volume, &value); + if (ret < 0) { + g_warning ("Failed to convert volume: %s", snd_strerror (ret)); + return FALSE; + } + + *decibel = value / 100.0; + return TRUE; +} + +static void +read_volume_data (snd_mixer_elem_t *el, AlsaControlData *data) +{ + glong volume; + glong min, max; + gint ret; + gint i; + + /* Read volume ranges, this call should never fail on valid input */ + ret = snd_mixer_selem_get_playback_volume_range (el, &min, &max); + if G_UNLIKELY (ret < 0) { + g_warning ("Failed to read playback volume range: %s", snd_strerror (ret)); + return; + } + data->min = (guint) min; + data->max = (guint) max; + + /* This fails when decibels are not supported */ + ret = snd_mixer_selem_get_playback_dB_range (el, &min, &max); + if (ret == 0) { + data->min_decibel = min / 100.0; + data->max_decibel = max / 100.0; + } else + data->min_decibel = data->max_decibel = -MATE_MIXER_INFINITY; + + for (i = 0; i < MATE_MIXER_CHANNEL_MAX; i++) + data->v[i] = data->min; + + data->volume = data->min; + data->volume_joined = snd_mixer_selem_has_playback_volume_joined (el); + + if (data->switch_usable == TRUE) + data->switch_joined = snd_mixer_selem_has_playback_switch_joined (el); + + if (snd_mixer_selem_is_playback_mono (el) == 1) { + /* Special handling for single channel controls */ + ret = snd_mixer_selem_get_playback_volume (el, SND_MIXER_SCHN_MONO, &volume); + if (ret == 0) { + data->channels = 1; + + data->c[0] = MATE_MIXER_CHANNEL_MONO; + data->v[0] = data->volume = (guint) volume; + } else { + g_warning ("Failed to read playback volume: %s", snd_strerror (ret)); + } + + if (data->switch_usable == TRUE) { + gint value; + + ret = snd_mixer_selem_get_playback_switch (el, SND_MIXER_SCHN_MONO, &value); + if G_LIKELY (ret == 0) + data->m[0] = !value; + } + } else { + snd_mixer_selem_channel_id_t channel; + + /* We use numeric channel indices, but ALSA only works with channel + * positions, go over all the positions supported by ALSA and create + * a list of channels */ + for (channel = 0; channel < SND_MIXER_SCHN_LAST; channel++) { + if (snd_mixer_selem_has_playback_channel (el, channel) == 0) + continue; + + if (data->switch_usable == TRUE) { + gint value; + + ret = snd_mixer_selem_get_playback_switch (el, channel, &value); + if (ret == 0) + data->m[channel] = !value; + } + + ret = snd_mixer_selem_get_playback_volume (el, channel, &volume); + if (ret < 0) { + g_warning ("Failed to read playback volume: %s", snd_strerror (ret)); + continue; + } + data->channels++; + + /* The single value volume is the highest channel volume */ + if (data->volume < volume) + data->volume = volume; + + data->c[channel] = alsa_channel_map_from[channel]; + data->v[channel] = (guint) volume; + } + } +} diff --git a/backends/alsa/alsa-stream-output-control.h b/backends/alsa/alsa-stream-output-control.h new file mode 100644 index 0000000..845eaae --- /dev/null +++ b/backends/alsa/alsa-stream-output-control.h @@ -0,0 +1,64 @@ +/* + * 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/>. + */ + +#ifndef ALSA_STREAM_OUTPUT_CONTROL_H +#define ALSA_STREAM_OUTPUT_CONTROL_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "alsa-stream-control.h" + +G_BEGIN_DECLS + +#define ALSA_TYPE_STREAM_OUTPUT_CONTROL \ + (alsa_stream_output_control_get_type ()) +#define ALSA_STREAM_OUTPUT_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_STREAM_OUTPUT_CONTROL, AlsaStreamOutputControl)) +#define ALSA_IS_STREAM_OUTPUT_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_STREAM_OUTPUT_CONTROL)) +#define ALSA_STREAM_OUTPUT_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), ALSA_TYPE_STREAM_OUTPUT_CONTROL, AlsaStreamOutputControlClass)) +#define ALSA_IS_STREAM_OUTPUT_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), ALSA_TYPE_STREAM_OUTPUT_CONTROL)) +#define ALSA_STREAM_OUTPUT_CONTROL_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), ALSA_TYPE_STREAM_OUTPUT_CONTROL, AlsaStreamOutputControlClass)) + +typedef struct _AlsaStreamOutputControl AlsaStreamOutputControl; +typedef struct _AlsaStreamOutputControlClass AlsaStreamOutputControlClass; +typedef struct _AlsaStreamOutputControlPrivate AlsaStreamOutputControlPrivate; + +struct _AlsaStreamOutputControl +{ + AlsaStreamControl parent; +}; + +struct _AlsaStreamOutputControlClass +{ + AlsaStreamControlClass parent_class; +}; + +GType alsa_stream_output_control_get_type (void) G_GNUC_CONST; + +AlsaStreamControl *alsa_stream_output_control_new (const gchar *name, + const gchar *label, + MateMixerStreamControlRole role); + +G_END_DECLS + +#endif /* ALSA_STREAM_OUTPUT_CONTROL_H */ diff --git a/backends/alsa/alsa-stream.c b/backends/alsa/alsa-stream.c new file mode 100644 index 0000000..d2f68d4 --- /dev/null +++ b/backends/alsa/alsa-stream.c @@ -0,0 +1,273 @@ +/* + * 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 <libmatemixer/matemixer.h> + +#include "alsa-element.h" +#include "alsa-stream.h" +#include "alsa-stream-control.h" +#include "alsa-switch.h" + +struct _AlsaStreamPrivate +{ + GHashTable *switches; + GHashTable *controls; + MateMixerStreamControl *control; +}; + +static void alsa_stream_class_init (AlsaStreamClass *klass); +static void alsa_stream_init (AlsaStream *stream); +static void alsa_stream_dispose (GObject *object); +static void alsa_stream_finalize (GObject *object); + +G_DEFINE_TYPE (AlsaStream, alsa_stream, MATE_MIXER_TYPE_STREAM) + +static MateMixerStreamControl *alsa_stream_get_control (MateMixerStream *mms, + const gchar *name); +static MateMixerStreamControl *alsa_stream_get_default_control (MateMixerStream *mms); + +static MateMixerSwitch * alsa_stream_get_switch (MateMixerStream *mms, + const gchar *name); + +static GList * alsa_stream_list_controls (MateMixerStream *mms); +static GList * alsa_stream_list_switches (MateMixerStream *mms); + +static void +alsa_stream_class_init (AlsaStreamClass *klass) +{ + GObjectClass *object_class; + MateMixerStreamClass *stream_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = alsa_stream_dispose; + object_class->finalize = alsa_stream_finalize; + + stream_class = MATE_MIXER_STREAM_CLASS (klass); + stream_class->get_control = alsa_stream_get_control; + stream_class->get_default_control = alsa_stream_get_default_control; + stream_class->get_switch = alsa_stream_get_switch; + stream_class->list_controls = alsa_stream_list_controls; + stream_class->list_switches = alsa_stream_list_switches; + + g_type_class_add_private (object_class, sizeof (AlsaStreamPrivate)); +} + +static void +alsa_stream_init (AlsaStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + ALSA_TYPE_STREAM, + AlsaStreamPrivate); + + stream->priv->controls = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + stream->priv->switches = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); +} + +static void +alsa_stream_dispose (GObject *object) +{ + AlsaStream *stream; + + stream = ALSA_STREAM (object); + + g_hash_table_remove_all (stream->priv->controls); + g_hash_table_remove_all (stream->priv->switches); + + g_clear_object (&stream->priv->control); + + G_OBJECT_CLASS (alsa_stream_parent_class)->dispose (object); +} + +static void +alsa_stream_finalize (GObject *object) +{ + AlsaStream *stream; + + stream = ALSA_STREAM (object); + + g_hash_table_destroy (stream->priv->controls); + g_hash_table_destroy (stream->priv->switches); + + G_OBJECT_CLASS (alsa_stream_parent_class)->finalize (object); +} + +AlsaStream * +alsa_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerStreamFlags flags) +{ + return g_object_new (ALSA_TYPE_STREAM, + "name", name, + "device", device, + "flags", flags, + NULL); +} + +void +alsa_stream_add_control (AlsaStream *stream, AlsaStreamControl *control) +{ + const gchar *name; + + g_return_if_fail (ALSA_IS_STREAM (stream)); + g_return_if_fail (ALSA_IS_STREAM_CONTROL (control)); + + name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (control)); + g_hash_table_insert (stream->priv->controls, + g_strdup (name), + g_object_ref (control)); +} + +void +alsa_stream_add_switch (AlsaStream *stream, AlsaSwitch *swtch) +{ + const gchar *name; + + g_return_if_fail (ALSA_IS_STREAM (stream)); + g_return_if_fail (ALSA_IS_SWITCH (swtch)); + + name = mate_mixer_switch_get_name (MATE_MIXER_SWITCH (swtch)); + g_hash_table_insert (stream->priv->switches, + g_strdup (name), + g_object_ref (swtch)); +} + +gboolean +alsa_stream_is_empty (AlsaStream *stream) +{ + g_return_val_if_fail (ALSA_IS_STREAM (stream), FALSE); + + if (g_hash_table_size (stream->priv->controls) > 0 || + g_hash_table_size (stream->priv->switches) > 0) + return FALSE; + + return TRUE; +} + +void +alsa_stream_set_default_control (AlsaStream *stream, AlsaStreamControl *control) +{ + g_return_if_fail (ALSA_IS_STREAM (stream)); + g_return_if_fail (ALSA_IS_STREAM_CONTROL (control)); + + /* This function is only used internally so avoid validating that the control + * belongs to this stream */ + if (stream->priv->control != NULL) + g_object_unref (stream->priv->control); + + if (control != NULL) + stream->priv->control = MATE_MIXER_STREAM_CONTROL (g_object_ref (control)); + else + stream->priv->control = NULL; +} + +void +alsa_stream_load_elements (AlsaStream *stream, const gchar *name) +{ + AlsaElement *element; + + g_return_if_fail (ALSA_IS_STREAM (stream)); + g_return_if_fail (name != NULL); + + element = g_hash_table_lookup (stream->priv->controls, name); + if (element != NULL) + alsa_element_load (element); + + element = g_hash_table_lookup (stream->priv->switches, name); + if (element != NULL) + alsa_element_load (element); +} + +gboolean +alsa_stream_remove_elements (AlsaStream *stream, const gchar *name) +{ + gboolean removed = FALSE; + + g_return_val_if_fail (ALSA_IS_STREAM (stream), FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + if (g_hash_table_remove (stream->priv->controls, name) == TRUE) + removed = TRUE; + if (g_hash_table_remove (stream->priv->switches, name) == TRUE) + removed = TRUE; + + return removed; +} + +static MateMixerStreamControl * +alsa_stream_get_control (MateMixerStream *mms, const gchar *name) +{ + g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); + + return g_hash_table_lookup (ALSA_STREAM (mms)->priv->controls, name); +} + +static MateMixerStreamControl * +alsa_stream_get_default_control (MateMixerStream *mms) +{ + g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); + + return ALSA_STREAM (mms)->priv->control; +} + +static MateMixerSwitch * +alsa_stream_get_switch (MateMixerStream *mms, const gchar *name) +{ + g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); + + return g_hash_table_lookup (ALSA_STREAM (mms)->priv->switches, name); +} + +static GList * +alsa_stream_list_controls (MateMixerStream *mms) +{ + GList *list; + + g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); + + /* Convert the hash table to a linked list, this list is expected to be + * cached in the main library */ + list = g_hash_table_get_values (ALSA_STREAM (mms)->priv->controls); + if (list != NULL) + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return list; +} + +static GList * +alsa_stream_list_switches (MateMixerStream *mms) +{ + GList *list; + + g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); + + /* Convert the hash table to a linked list, this list is expected to be + * cached in the main library */ + list = g_hash_table_get_values (ALSA_STREAM (mms)->priv->switches); + if (list != NULL) + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return list; +} diff --git a/backends/alsa/alsa-stream.h b/backends/alsa/alsa-stream.h new file mode 100644 index 0000000..f26a643 --- /dev/null +++ b/backends/alsa/alsa-stream.h @@ -0,0 +1,88 @@ +/* + * 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/>. + */ + +#ifndef ALSA_STREAM_H +#define ALSA_STREAM_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "alsa-element.h" +#include "alsa-stream-control.h" +#include "alsa-switch.h" + +G_BEGIN_DECLS + +#define ALSA_TYPE_STREAM \ + (alsa_stream_get_type ()) +#define ALSA_STREAM(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_STREAM, AlsaStream)) +#define ALSA_IS_STREAM(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_STREAM)) +#define ALSA_STREAM_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), ALSA_TYPE_STREAM, AlsaStreamClass)) +#define ALSA_IS_STREAM_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), ALSA_TYPE_STREAM)) +#define ALSA_STREAM_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), ALSA_TYPE_STREAM, AlsaStreamClass)) + +typedef struct _AlsaStream AlsaStream; +typedef struct _AlsaStreamClass AlsaStreamClass; +typedef struct _AlsaStreamPrivate AlsaStreamPrivate; + +struct _AlsaStream +{ + MateMixerStream parent; + + /*< private >*/ + AlsaStreamPrivate *priv; +}; + +struct _AlsaStreamClass +{ + MateMixerStreamClass parent_class; +}; + +GType alsa_stream_get_type (void) G_GNUC_CONST; + +AlsaStream *alsa_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerStreamFlags flags); + +void alsa_stream_add_control (AlsaStream *stream, + AlsaStreamControl *control); + +void alsa_stream_add_switch (AlsaStream *stream, + AlsaSwitch *swtch); + +gboolean alsa_stream_is_empty (AlsaStream *stream); + +void alsa_stream_set_default_control (AlsaStream *stream, + AlsaStreamControl *control); + +void alsa_stream_load_elements (AlsaStream *stream, + const gchar *name); + +gboolean alsa_stream_remove_elements (AlsaStream *stream, + const gchar *name); + +void alsa_stream_remove_all (AlsaStream *stream); + +G_END_DECLS + +#endif /* ALSA_STREAM_H */ diff --git a/backends/alsa/alsa-switch-option.c b/backends/alsa/alsa-switch-option.c new file mode 100644 index 0000000..2173113 --- /dev/null +++ b/backends/alsa/alsa-switch-option.c @@ -0,0 +1,74 @@ +/* + * 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 <alsa/asoundlib.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "alsa-switch-option.h" + +struct _AlsaSwitchOptionPrivate +{ + guint id; +}; + +static void alsa_switch_option_class_init (AlsaSwitchOptionClass *klass); +static void alsa_switch_option_init (AlsaSwitchOption *option); + +G_DEFINE_TYPE (AlsaSwitchOption, alsa_switch_option, MATE_MIXER_TYPE_SWITCH_OPTION) + +static void +alsa_switch_option_class_init (AlsaSwitchOptionClass *klass) +{ + g_type_class_add_private (G_OBJECT_CLASS (klass), sizeof (AlsaSwitchOptionPrivate)); +} + +static void +alsa_switch_option_init (AlsaSwitchOption *option) +{ + option->priv = G_TYPE_INSTANCE_GET_PRIVATE (option, + ALSA_TYPE_SWITCH_OPTION, + AlsaSwitchOptionPrivate); +} + +AlsaSwitchOption * +alsa_switch_option_new (const gchar *name, + const gchar *label, + const gchar *icon, + guint id) +{ + AlsaSwitchOption *option; + + option = g_object_new (ALSA_TYPE_SWITCH_OPTION, + "name", name, + "label", label, + NULL); + + option->priv->id = id; + return option; +} + +guint +alsa_switch_option_get_id (AlsaSwitchOption *option) +{ + g_return_val_if_fail (ALSA_IS_SWITCH_OPTION (option), 0); + + return option->priv->id; +} diff --git a/backends/alsa/alsa-switch-option.h b/backends/alsa/alsa-switch-option.h new file mode 100644 index 0000000..c2dda87 --- /dev/null +++ b/backends/alsa/alsa-switch-option.h @@ -0,0 +1,68 @@ +/* + * 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/>. + */ + +#ifndef ALSA_SWITCH_OPTION_H +#define ALSA_SWITCH_OPTION_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +G_BEGIN_DECLS + +#define ALSA_TYPE_SWITCH_OPTION \ + (alsa_switch_option_get_type ()) +#define ALSA_SWITCH_OPTION(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_SWITCH_OPTION, AlsaSwitchOption)) +#define ALSA_IS_SWITCH_OPTION(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_SWITCH_OPTION)) +#define ALSA_SWITCH_OPTION_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), ALSA_TYPE_SWITCH_OPTION, AlsaSwitchOptionClass)) +#define ALSA_IS_SWITCH_OPTION_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), ALSA_TYPE_SWITCH_OPTION)) +#define ALSA_SWITCH_OPTION_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), ALSA_TYPE_SWITCH_OPTION, AlsaSwitchOptionClass)) + +typedef struct _AlsaSwitchOption AlsaSwitchOption; +typedef struct _AlsaSwitchOptionClass AlsaSwitchOptionClass; +typedef struct _AlsaSwitchOptionPrivate AlsaSwitchOptionPrivate; + +struct _AlsaSwitchOption +{ + MateMixerSwitchOption parent; + + /*< private >*/ + AlsaSwitchOptionPrivate *priv; +}; + +struct _AlsaSwitchOptionClass +{ + MateMixerSwitchOptionClass parent_class; +}; + +GType alsa_switch_option_get_type (void) G_GNUC_CONST; + +AlsaSwitchOption *alsa_switch_option_new (const gchar *name, + const gchar *label, + const gchar *icon, + guint id); + +guint alsa_switch_option_get_id (AlsaSwitchOption *option); + +G_END_DECLS + +#endif /* ALSA_SWITCH_OPTION_H */ diff --git a/backends/alsa/alsa-switch.c b/backends/alsa/alsa-switch.c new file mode 100644 index 0000000..15151ae --- /dev/null +++ b/backends/alsa/alsa-switch.c @@ -0,0 +1,227 @@ +/* + * 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 <alsa/asoundlib.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "alsa-element.h" +#include "alsa-switch.h" +#include "alsa-switch-option.h" + +struct _AlsaSwitchPrivate +{ + GList *options; + guint32 channel_mask; + snd_mixer_elem_t *element; +}; + +static void alsa_element_interface_init (AlsaElementInterface *iface); + +static void alsa_switch_class_init (AlsaSwitchClass *klass); +static void alsa_switch_init (AlsaSwitch *swtch); + +G_DEFINE_TYPE_WITH_CODE (AlsaSwitch, alsa_switch, + MATE_MIXER_TYPE_SWITCH, + G_IMPLEMENT_INTERFACE (ALSA_TYPE_ELEMENT, + alsa_element_interface_init)) + +static gboolean alsa_switch_set_active_option (MateMixerSwitch *mms, + MateMixerSwitchOption *mmso); + +static GList * alsa_switch_list_options (MateMixerSwitch *mms); + +static snd_mixer_elem_t * alsa_switch_get_snd_element (AlsaElement *element); +static void alsa_switch_set_snd_element (AlsaElement *element, + snd_mixer_elem_t *el); +static gboolean alsa_switch_load (AlsaElement *element); + +static void +alsa_element_interface_init (AlsaElementInterface *iface) +{ + iface->get_snd_element = alsa_switch_get_snd_element; + iface->set_snd_element = alsa_switch_set_snd_element; + iface->load = alsa_switch_load; +} + +static void +alsa_switch_class_init (AlsaSwitchClass *klass) +{ + MateMixerSwitchClass *switch_class; + + switch_class = MATE_MIXER_SWITCH_CLASS (klass); + switch_class->set_active_option = alsa_switch_set_active_option; + switch_class->list_options = alsa_switch_list_options; + + g_type_class_add_private (G_OBJECT_CLASS (klass), sizeof (AlsaSwitchPrivate)); +} + +static void +alsa_switch_init (AlsaSwitch *swtch) +{ + swtch->priv = G_TYPE_INSTANCE_GET_PRIVATE (swtch, + ALSA_TYPE_SWITCH, + AlsaSwitchPrivate); +} + +AlsaSwitch * +alsa_switch_new (const gchar *name, const gchar *label, GList *options) +{ + AlsaSwitch *swtch; + + swtch = g_object_new (ALSA_TYPE_SWITCH, + "name", name, + "label", label, + NULL); + + /* Takes ownership of options */ + swtch->priv->options = options; + return swtch; +} + +static gboolean +alsa_switch_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso) +{ + AlsaSwitch *swtch; + guint index; + gboolean set_item = FALSE; + snd_mixer_selem_channel_id_t channel; + + g_return_val_if_fail (ALSA_IS_SWITCH (mms), FALSE); + g_return_val_if_fail (ALSA_IS_SWITCH_OPTION (mmso), FALSE); + + swtch = ALSA_SWITCH (mms); + + /* The channel mask is created when reading the active option the first + * time, so a successful load must be done before changing the option */ + if G_UNLIKELY (swtch->priv->channel_mask == 0) { + g_debug ("Not setting active switch option, channel mask unknown"); + return FALSE; + } + + index = alsa_switch_option_get_id (ALSA_SWITCH_OPTION (mmso)); + + for (channel = 0; channel < SND_MIXER_SCHN_LAST; channel++) { + /* The option is set per-channel, make sure to set it only for channels + * we successfully read the value from */ + if (swtch->priv->channel_mask & (1 << channel)) { + gint ret = snd_mixer_selem_set_enum_item (swtch->priv->element, + channel, + index); + if (ret == 0) + set_item = TRUE; + else + g_warning ("Failed to set active option of switch %s: %s", + snd_mixer_selem_get_name (swtch->priv->element), + snd_strerror (ret)); + } + } + return set_item; +} + +static GList * +alsa_switch_list_options (MateMixerSwitch *swtch) +{ + g_return_val_if_fail (ALSA_IS_SWITCH (swtch), NULL); + + return ALSA_SWITCH (swtch)->priv->options; +} + +static snd_mixer_elem_t * +alsa_switch_get_snd_element (AlsaElement *element) +{ + g_return_val_if_fail (ALSA_IS_SWITCH (element), NULL); + + return ALSA_SWITCH (element)->priv->element; +} + +static void +alsa_switch_set_snd_element (AlsaElement *element, snd_mixer_elem_t *el) +{ + g_return_if_fail (ALSA_IS_SWITCH (element)); + g_return_if_fail (el != NULL); + + ALSA_SWITCH (element)->priv->element = el; +} + +static gboolean +alsa_switch_load (AlsaElement *element) +{ + AlsaSwitch *swtch; + GList *list; + guint item; + gint ret; + snd_mixer_selem_channel_id_t c; + + swtch = ALSA_SWITCH (element); + + /* When reading the first time we try all the channels, otherwise only the + * ones which returned success before */ + if (swtch->priv->channel_mask == 0) { + for (c = 0; c < SND_MIXER_SCHN_LAST; c++) { + ret = snd_mixer_selem_get_enum_item (swtch->priv->element, c, &item); + + /* The active enum option is set per-channel, so when reading it the + * first time, create a mask of all channels for which we read the + * value successfully */ + if (ret == 0) + swtch->priv->channel_mask |= 1 << c; + } + + /* The last ALSA call might have failed, but it doesn't matter if we have + * a channel mask */ + if (swtch->priv->channel_mask > 0) + ret = 0; + } else { + for (c = 0; !(swtch->priv->channel_mask & (1 << c)); c++) + ; + + /* When not reading the mask, the first usable channel is enough, we don't + * support per-channel selections anyway */ + ret = snd_mixer_selem_get_enum_item (swtch->priv->element, c, &item); + } + + if (ret < 0) { + g_warning ("Failed to read active option of switch %s: %s", + snd_mixer_selem_get_name (swtch->priv->element), + snd_strerror (ret)); + return FALSE; + } + + list = swtch->priv->options; + while (list != NULL) { + AlsaSwitchOption *option = ALSA_SWITCH_OPTION (list->data); + + /* Mark the selected option when we find it, ALSA indentifies them + * by numeric indices */ + if (alsa_switch_option_get_id (option) == item) { + _mate_mixer_switch_set_active_option (MATE_MIXER_SWITCH (swtch), + MATE_MIXER_SWITCH_OPTION (option)); + return TRUE; + } + list = list->next; + } + + g_warning ("Unknown active option of switch %s: %d", + snd_mixer_selem_get_name (swtch->priv->element), + item); + + return FALSE; +} diff --git a/backends/alsa/alsa-switch.h b/backends/alsa/alsa-switch.h new file mode 100644 index 0000000..fdcfb87 --- /dev/null +++ b/backends/alsa/alsa-switch.h @@ -0,0 +1,65 @@ +/* + * 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/>. + */ + +#ifndef ALSA_SWITCH_H +#define ALSA_SWITCH_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +G_BEGIN_DECLS + +#define ALSA_TYPE_SWITCH \ + (alsa_switch_get_type ()) +#define ALSA_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_SWITCH, AlsaSwitch)) +#define ALSA_IS_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_SWITCH)) +#define ALSA_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), ALSA_TYPE_SWITCH, AlsaSwitchClass)) +#define ALSA_IS_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), ALSA_TYPE_SWITCH)) +#define ALSA_SWITCH_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), ALSA_TYPE_SWITCH, AlsaSwitchClass)) + +typedef struct _AlsaSwitch AlsaSwitch; +typedef struct _AlsaSwitchClass AlsaSwitchClass; +typedef struct _AlsaSwitchPrivate AlsaSwitchPrivate; + +struct _AlsaSwitch +{ + MateMixerSwitch parent; + + /*< private >*/ + AlsaSwitchPrivate *priv; +}; + +struct _AlsaSwitchClass +{ + MateMixerSwitchClass parent_class; +}; + +GType alsa_switch_get_type (void) G_GNUC_CONST; + +AlsaSwitch *alsa_switch_new (const gchar *name, + const gchar *label, + GList *options); + +G_END_DECLS + +#endif /* ALSA_SWITCH_H */ diff --git a/backends/alsa/alsa-toggle.c b/backends/alsa/alsa-toggle.c new file mode 100644 index 0000000..efa3460 --- /dev/null +++ b/backends/alsa/alsa-toggle.c @@ -0,0 +1,219 @@ +/* + * 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 <alsa/asoundlib.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "alsa-element.h" +#include "alsa-switch-option.h" +#include "alsa-toggle.h" + +struct _AlsaTogglePrivate +{ + AlsaToggleType type; + guint32 channel_mask; + snd_mixer_elem_t *element; +}; + +static void alsa_element_interface_init (AlsaElementInterface *iface); + +static void alsa_toggle_class_init (AlsaToggleClass *klass); +static void alsa_toggle_init (AlsaToggle *toggle); + +G_DEFINE_TYPE_WITH_CODE (AlsaToggle, alsa_toggle, MATE_MIXER_TYPE_TOGGLE, + G_IMPLEMENT_INTERFACE (ALSA_TYPE_ELEMENT, + alsa_element_interface_init)) + +static gboolean alsa_toggle_set_active_option (MateMixerSwitch *mms, + MateMixerSwitchOption *mmso); + +static snd_mixer_elem_t * alsa_toggle_get_snd_element (AlsaElement *element); +static void alsa_toggle_set_snd_element (AlsaElement *element, + snd_mixer_elem_t *el); +static gboolean alsa_toggle_load (AlsaElement *element); + +static void +alsa_element_interface_init (AlsaElementInterface *iface) +{ + iface->get_snd_element = alsa_toggle_get_snd_element; + iface->set_snd_element = alsa_toggle_set_snd_element; + iface->load = alsa_toggle_load; +} + +static void +alsa_toggle_class_init (AlsaToggleClass *klass) +{ + MateMixerSwitchClass *switch_class; + + switch_class = MATE_MIXER_SWITCH_CLASS (klass); + switch_class->set_active_option = alsa_toggle_set_active_option; + + g_type_class_add_private (G_OBJECT_CLASS (klass), sizeof (AlsaTogglePrivate)); +} + +static void +alsa_toggle_init (AlsaToggle *toggle) +{ + toggle->priv = G_TYPE_INSTANCE_GET_PRIVATE (toggle, + ALSA_TYPE_TOGGLE, + AlsaTogglePrivate); +} + +AlsaToggle * +alsa_toggle_new (const gchar *name, + const gchar *label, + AlsaToggleType type, + AlsaSwitchOption *on, + AlsaSwitchOption *off) +{ + AlsaToggle *toggle; + + toggle = g_object_new (ALSA_TYPE_TOGGLE, + "name", name, + "label", label, + "state-option-on", on, + "state-option-off", off, + NULL); + + toggle->priv->type = type; + return toggle; +} + +static gboolean +alsa_toggle_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso) +{ + AlsaToggle *toggle; + gint value; + gint ret; + + g_return_val_if_fail (ALSA_IS_TOGGLE (mms), FALSE); + g_return_val_if_fail (ALSA_IS_SWITCH_OPTION (mmso), FALSE); + + toggle = ALSA_TOGGLE (mms); + + /* For toggles the 0/1 value is stored as the switch option id */ + value = alsa_switch_option_get_id (ALSA_SWITCH_OPTION (mmso)); + if G_UNLIKELY (value != 0 && value != 1) { + g_warn_if_reached (); + return FALSE; + } + + if (toggle->priv->type == ALSA_TOGGLE_CAPTURE) + ret = snd_mixer_selem_set_capture_switch_all (toggle->priv->element, value); + else + ret = snd_mixer_selem_set_playback_switch_all (toggle->priv->element, value); + + if (ret < 0) { + g_warning ("Failed to set value of toggle %s: %s", + snd_mixer_selem_get_name (toggle->priv->element), + snd_strerror (ret)); + return FALSE; + } + + return TRUE; +} + +static snd_mixer_elem_t * +alsa_toggle_get_snd_element (AlsaElement *element) +{ + g_return_val_if_fail (ALSA_IS_TOGGLE (element), NULL); + + return ALSA_TOGGLE (element)->priv->element; +} + +static void +alsa_toggle_set_snd_element (AlsaElement *element, snd_mixer_elem_t *el) +{ + g_return_if_fail (ALSA_IS_TOGGLE (element)); + g_return_if_fail (el != NULL); + + ALSA_TOGGLE (element)->priv->element = el; +} + +static gboolean +alsa_toggle_load (AlsaElement *element) +{ + AlsaToggle *toggle; + gint value; + gint ret; + snd_mixer_selem_channel_id_t c; + + toggle = ALSA_TOGGLE (element); + + /* When reading the first time we try all the channels, otherwise only the + * ones which returned success before */ + if (toggle->priv->channel_mask == 0) { + for (c = 0; c < SND_MIXER_SCHN_LAST; c++) { + if (toggle->priv->type == ALSA_TOGGLE_CAPTURE) + ret = snd_mixer_selem_get_capture_switch (toggle->priv->element, + c, + &value); + else + ret = snd_mixer_selem_get_playback_switch (toggle->priv->element, + c, + &value); + + /* The active enum option is set per-channel, so when reading it the + * first time, create a mask of all channels for which we read the + * value successfully */ + if (ret == 0) + toggle->priv->channel_mask |= 1 << c; + } + + /* The last ALSA call might have failed, but it doesn't matter if we have + * a channel mask */ + if (toggle->priv->channel_mask > 0) + ret = 0; + } else { + for (c = 0; !(toggle->priv->channel_mask & (1 << c)); c++) + ; + + /* When not reading the mask, the first usable channel is enough, we don't + * support per-channel selections anyway */ + if (toggle->priv->type == ALSA_TOGGLE_CAPTURE) + ret = snd_mixer_selem_get_capture_switch (toggle->priv->element, + c, + &value); + else + ret = snd_mixer_selem_get_playback_switch (toggle->priv->element, + c, + &value); + } + + if (ret == 0) { + MateMixerSwitchOption *active; + + if (value > 0) + active = mate_mixer_toggle_get_state_option (MATE_MIXER_TOGGLE (toggle), TRUE); + else + active = mate_mixer_toggle_get_state_option (MATE_MIXER_TOGGLE (toggle), FALSE); + + _mate_mixer_switch_set_active_option (MATE_MIXER_SWITCH (toggle), active); + + return TRUE; + } + + g_warning ("Failed to read state of toggle %s: %s", + snd_mixer_selem_get_name (toggle->priv->element), + snd_strerror (ret)); + + return FALSE; +} diff --git a/backends/alsa/alsa-toggle.h b/backends/alsa/alsa-toggle.h new file mode 100644 index 0000000..d9c083b --- /dev/null +++ b/backends/alsa/alsa-toggle.h @@ -0,0 +1,74 @@ +/* + * 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/>. + */ + +#ifndef ALSA_TOGGLE_H +#define ALSA_TOGGLE_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "alsa-switch-option.h" + +G_BEGIN_DECLS + +typedef enum { + ALSA_TOGGLE_CAPTURE, + ALSA_TOGGLE_PLAYBACK +} AlsaToggleType; + +#define ALSA_TYPE_TOGGLE \ + (alsa_toggle_get_type ()) +#define ALSA_TOGGLE(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_TOGGLE, AlsaToggle)) +#define ALSA_IS_TOGGLE(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_TOGGLE)) +#define ALSA_TOGGLE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), ALSA_TYPE_TOGGLE, AlsaToggleClass)) +#define ALSA_IS_TOGGLE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), ALSA_TYPE_TOGGLE)) +#define ALSA_TOGGLE_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), ALSA_TYPE_TOGGLE, AlsaToggleClass)) + +typedef struct _AlsaToggle AlsaToggle; +typedef struct _AlsaToggleClass AlsaToggleClass; +typedef struct _AlsaTogglePrivate AlsaTogglePrivate; + +struct _AlsaToggle +{ + MateMixerToggle parent; + + /*< private >*/ + AlsaTogglePrivate *priv; +}; + +struct _AlsaToggleClass +{ + MateMixerToggleClass parent_class; +}; + +GType alsa_toggle_get_type (void) G_GNUC_CONST; + +AlsaToggle *alsa_toggle_new (const gchar *name, + const gchar *label, + AlsaToggleType type, + AlsaSwitchOption *on, + AlsaSwitchOption *off); + +G_END_DECLS + +#endif /* ALSA_TOGGLE_H */ |