diff options
Diffstat (limited to 'backends/alsa')
-rw-r--r-- | backends/alsa/Makefile.am | 1 | ||||
-rw-r--r-- | backends/alsa/alsa-backend.c | 333 | ||||
-rw-r--r-- | backends/alsa/alsa-constants.c | 189 | ||||
-rw-r--r-- | backends/alsa/alsa-constants.h | 21 | ||||
-rw-r--r-- | backends/alsa/alsa-device.c | 690 | ||||
-rw-r--r-- | backends/alsa/alsa-device.h | 4 | ||||
-rw-r--r-- | backends/alsa/alsa-element.c | 16 | ||||
-rw-r--r-- | backends/alsa/alsa-element.h | 3 | ||||
-rw-r--r-- | backends/alsa/alsa-stream-control.c | 110 | ||||
-rw-r--r-- | backends/alsa/alsa-stream-control.h | 6 | ||||
-rw-r--r-- | backends/alsa/alsa-stream-input-control.c | 12 | ||||
-rw-r--r-- | backends/alsa/alsa-stream-input-control.h | 4 | ||||
-rw-r--r-- | backends/alsa/alsa-stream-output-control.c | 12 | ||||
-rw-r--r-- | backends/alsa/alsa-stream-output-control.h | 4 | ||||
-rw-r--r-- | backends/alsa/alsa-stream.c | 335 | ||||
-rw-r--r-- | backends/alsa/alsa-stream.h | 41 | ||||
-rw-r--r-- | backends/alsa/alsa-switch-option.c | 3 | ||||
-rw-r--r-- | backends/alsa/alsa-switch-option.h | 12 | ||||
-rw-r--r-- | backends/alsa/alsa-switch.c | 47 | ||||
-rw-r--r-- | backends/alsa/alsa-switch.h | 7 | ||||
-rw-r--r-- | backends/alsa/alsa-toggle.c | 37 | ||||
-rw-r--r-- | backends/alsa/alsa-toggle.h | 11 |
22 files changed, 1290 insertions, 608 deletions
diff --git a/backends/alsa/Makefile.am b/backends/alsa/Makefile.am index 220bb3b..48dcaba 100644 --- a/backends/alsa/Makefile.am +++ b/backends/alsa/Makefile.am @@ -3,6 +3,7 @@ backenddir = $(libdir)/libmatemixer backend_LTLIBRARIES = libmatemixer-alsa.la AM_CPPFLAGS = \ + -Wno-unknown-pragmas \ -I$(top_srcdir) \ -DG_LOG_DOMAIN=\"libmatemixer-alsa\" diff --git a/backends/alsa/alsa-backend.c b/backends/alsa/alsa-backend.c index 7a17b85..6bac691 100644 --- a/backends/alsa/alsa-backend.c +++ b/backends/alsa/alsa-backend.c @@ -27,12 +27,22 @@ #include "alsa-stream.h" #define BACKEND_NAME "ALSA" -#define BACKEND_PRIORITY 9 +#define BACKEND_PRIORITY 20 + +#define ALSA_DEVICE_GET_ID(d) \ + (g_object_get_data (G_OBJECT (d), "__matemixer_alsa_device_id")) + +#define ALSA_DEVICE_SET_ID(d,id) \ + (g_object_set_data_full (G_OBJECT (d), \ + "__matemixer_alsa_device_id", \ + g_strdup (id), \ + g_free)) struct _AlsaBackendPrivate { GSource *timeout_source; - GHashTable *devices; + GList *streams; + GList *devices; GHashTable *devices_ids; }; @@ -47,26 +57,38 @@ static void alsa_backend_finalize (GObject *object); 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 alsa_backend_open (MateMixerBackend *backend); +static void alsa_backend_close (MateMixerBackend *backend); +static const GList *alsa_backend_list_devices (MateMixerBackend *backend); +static const 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 gboolean read_devices (AlsaBackend *alsa); +static void remove_device (AlsaBackend *alsa, + AlsaDevice *device); +static void remove_device_by_name (AlsaBackend *alsa, + const gchar *name); +static void remove_device_by_list_item (AlsaBackend *alsa, + GList *item); -static gboolean read_device (AlsaBackend *alsa, - const gchar *card); +static void remove_stream (AlsaBackend *alsa, + const gchar *name); -static void add_device (AlsaBackend *alsa, - AlsaDevice *device); +static void select_default_input_stream (AlsaBackend *alsa); +static void select_default_output_stream (AlsaBackend *alsa); -static void remove_device (AlsaBackend *alsa, - AlsaDevice *device); -static void remove_stream (AlsaBackend *alsa, - const gchar *name); +static void free_stream_list (AlsaBackend *alsa); -static void select_default_input_stream (AlsaBackend *alsa); -static void select_default_output_stream (AlsaBackend *alsa); +static gint compare_devices (gconstpointer a, + gconstpointer b); +static gint compare_device_name (gconstpointer a, + gconstpointer b); static MateMixerBackendInfo info; @@ -118,11 +140,6 @@ alsa_backend_init (AlsaBackend *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, @@ -151,7 +168,6 @@ alsa_backend_finalize (GObject *object) 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); @@ -166,9 +182,8 @@ alsa_backend_open (MateMixerBackend *backend) 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 */ + /* Poll ALSA for changes every second, this only discovers added or removed + * 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, @@ -197,61 +212,60 @@ alsa_backend_close (MateMixerBackend *backend) g_source_destroy (alsa->priv->timeout_source); - g_hash_table_remove_all (alsa->priv->devices); + if (alsa->priv->devices != NULL) { + g_list_free_full (alsa->priv->devices, g_object_unref); + alsa->priv->devices = NULL; + } + + free_stream_list (alsa); + g_hash_table_remove_all (alsa->priv->devices_ids); _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_IDLE); } -static GList * +static const 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; + return ALSA_BACKEND (backend)->priv->devices; } -static GList * +static const GList * alsa_backend_list_streams (MateMixerBackend *backend) { - AlsaBackend *alsa; - GHashTableIter iter; - gpointer value; - GList *list = NULL; + AlsaBackend *alsa; 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 (alsa->priv->streams == NULL) { + GList *list; + + /* Walk through the list of devices and create the stream list, each + * device has at most one input and one output stream */ + list = alsa->priv->devices; + + while (list != NULL) { + AlsaDevice *device = ALSA_DEVICE (list->data); + AlsaStream *stream; + + stream = alsa_device_get_input_stream (device); + if (stream != NULL) { + alsa->priv->streams = + g_list_append (alsa->priv->streams, g_object_ref (stream)); + } + stream = alsa_device_get_output_stream (device); + if (stream != NULL) { + alsa->priv->streams = + g_list_append (alsa->priv->streams, g_object_ref (stream)); + } + list = list->next; + } } - - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + return alsa->priv->streams; } static gboolean @@ -260,12 +274,12 @@ read_devices (AlsaBackend *alsa) gint num; gint ret; gchar card[16]; - gboolean changed = FALSE; + gboolean added = 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; + added = TRUE; for (num = -1;;) { /* Read number of the next sound card */ @@ -277,12 +291,12 @@ read_devices (AlsaBackend *alsa) g_snprintf (card, sizeof (card), "hw:%d", num); if (read_device (alsa, card) == TRUE) - changed = TRUE; + added = TRUE; } /* If any card has been added, make sure we have the most suitable default * input and output streams */ - if (changed == TRUE) { + if (added == TRUE) { select_default_input_stream (alsa); select_default_output_stream (alsa); } @@ -300,15 +314,13 @@ read_device (AlsaBackend *alsa, const gchar *card) /* 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); + + remove_device_by_name (alsa, card); return FALSE; } @@ -317,9 +329,8 @@ read_device (AlsaBackend *alsa, const gchar *card) 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); + remove_device_by_name (alsa, card); snd_ctl_close (ctl); return FALSE; } @@ -342,11 +353,7 @@ read_device (AlsaBackend *alsa, const gchar *card) return FALSE; } - g_object_set_data_full (G_OBJECT (device), - "__matemixer_alsa_device_id", - g_strdup (id), - g_free); - + ALSA_DEVICE_SET_ID (device, id); add_device (alsa, device); snd_ctl_close (ctl); @@ -356,19 +363,13 @@ read_device (AlsaBackend *alsa, const gchar *card) static void add_device (AlsaBackend *alsa, AlsaDevice *device) { - const gchar *name; - - name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + alsa->priv->devices = g_list_insert_sorted_with_data (alsa->priv->devices, + device, + (GCompareDataFunc) compare_devices, + NULL); - 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"))); + /* Keep track of device identifiers */ + g_hash_table_add (alsa->priv->devices_ids, g_strdup (ALSA_DEVICE_GET_ID (device))); g_signal_connect_swapped (G_OBJECT (device), "closed", @@ -379,7 +380,22 @@ add_device (AlsaBackend *alsa, AlsaDevice *device) G_CALLBACK (remove_stream), alsa); - g_signal_emit_by_name (G_OBJECT (alsa), "device-added", name); + g_signal_connect_swapped (G_OBJECT (device), + "closed", + G_CALLBACK (free_stream_list), + alsa); + g_signal_connect_swapped (G_OBJECT (device), + "stream-added", + G_CALLBACK (free_stream_list), + alsa); + g_signal_connect_swapped (G_OBJECT (device), + "stream-removed", + G_CALLBACK (free_stream_list), + alsa); + + g_signal_emit_by_name (G_OBJECT (alsa), + "device-added", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); /* Load the device elements after emitting device-added, because the load * function will most likely emit stream-added on the device and backend */ @@ -389,27 +405,48 @@ add_device (AlsaBackend *alsa, AlsaDevice *device) static void remove_device (AlsaBackend *alsa, AlsaDevice *device) { - const gchar *name; + GList *item; + + item = g_list_find (alsa->priv->devices, device); + if (item != NULL) + remove_device_by_list_item (alsa, item); +} + +static void +remove_device_by_name (AlsaBackend *alsa, const gchar *name) +{ + GList *item; + + item = g_list_find_custom (alsa->priv->devices, name, compare_device_name); + if (item != NULL) + remove_device_by_list_item (alsa, item); +} + +static void +remove_device_by_list_item (AlsaBackend *alsa, GList *item) +{ + AlsaDevice *device; + + device = ALSA_DEVICE (item->data); - name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + g_signal_handlers_disconnect_by_data (G_OBJECT (device), alsa); - 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); + if (alsa_device_is_open (device) == TRUE) + alsa_device_close (device); + + alsa->priv->devices = g_list_delete_link (alsa->priv->devices, item); - /* Remove the device */ g_hash_table_remove (alsa->priv->devices_ids, - g_object_get_data (G_OBJECT (device), - "__matemixer_alsa_device_id")); + ALSA_DEVICE_GET_ID (device)); + + /* The list may and may not have been invalidate by device signals */ + free_stream_list (alsa); - // 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); + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + + g_object_unref (device); } static void @@ -419,7 +456,6 @@ remove_stream (AlsaBackend *alsa, const gchar *name) 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); @@ -432,36 +468,19 @@ remove_stream (AlsaBackend *alsa, const gchar *name) 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; - } - } + GList *list; - /* 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); + list = alsa->priv->devices; + while (list != NULL) { + AlsaDevice *device = ALSA_DEVICE (list->data); + AlsaStream *stream = alsa_device_get_input_stream (device); - 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; } + list = list->next; } /* In the worst case unset the default stream */ @@ -471,38 +490,50 @@ select_default_input_stream (AlsaBackend *alsa) 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; - } - } + GList *list; - /* 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); + list = alsa->priv->devices; + while (list != NULL) { + AlsaDevice *device = ALSA_DEVICE (list->data); + AlsaStream *stream = alsa_device_get_output_stream (device); - 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; } + list = list->next; } /* In the worst case unset the default stream */ _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (alsa), NULL); } + +static void +free_stream_list (AlsaBackend *alsa) +{ + if (alsa->priv->streams == NULL) + return; + + g_list_free_full (alsa->priv->streams, g_object_unref); + + alsa->priv->streams = NULL; +} + +static gint +compare_devices (gconstpointer a, gconstpointer b) +{ + MateMixerDevice *d1 = MATE_MIXER_DEVICE (a); + MateMixerDevice *d2 = MATE_MIXER_DEVICE (b); + + return strcmp (mate_mixer_device_get_name (d1), mate_mixer_device_get_name (d2)); +} + +static gint +compare_device_name (gconstpointer a, gconstpointer b) +{ + MateMixerDevice *device = MATE_MIXER_DEVICE (a); + const gchar *name = (const gchar *) b; + + return strcmp (mate_mixer_device_get_name (device), name); +} diff --git a/backends/alsa/alsa-constants.c b/backends/alsa/alsa-constants.c index 2124a2e..7b7021f 100644 --- a/backends/alsa/alsa-constants.c +++ b/backends/alsa/alsa-constants.c @@ -22,15 +22,190 @@ #include "alsa-constants.h" -// XXX add more and probably move them somewhere else +/* + * These lists of ALSA mixer elements are based on PulseAudio's mixer paths and own + * observations. The intention is to provide translatable and in some cases better + * readable labels and role assignments. The controls list is also used for selecting + * the default controls and the selection machanism relies on the order of elements, + * so more useful elements should be placed on the top. The last two boolean values + * indicate whether we prefer the element to be used as a default input or output + * control. + * + * Of course the lists are very incomplete and it would be great if users validated and + * refreshed them from time to time. + */ 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 }, + /* Output controls */ + { "Master", N_("Master"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, FALSE, TRUE }, + { "Hardware Master", N_("Hardware Master"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, FALSE, TRUE }, + { "PCM", N_("PCM"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, FALSE, TRUE }, + { "Desktop Speaker", N_("Desktop Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Speaker", N_("Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Front", N_("Front Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Front Speaker", N_("Front Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Speaker Front", N_("Front Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Headphone", N_("Headphone"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Headphone2", N_("Headphone 2"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Headset", N_("Headset"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Master Surround", N_("Surround Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Surround", N_("Surround Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Surround Speaker", N_("Surround Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Speaker Surround", N_("Surround Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Center", N_("Center Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Center Speaker", N_("Center Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "CLFE", N_("CLFE Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Speaker CLFE", N_("CLFE Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Master Mono", N_("Master Mono"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, FALSE, TRUE }, + { "Master Digital", N_("Master Digital"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, FALSE, TRUE }, + { "Digital/SPDIF", N_("Digital"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, FALSE, TRUE }, + { "Speaker Side", N_("Side Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Side", N_("Side Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Rear", N_("Rear Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, TRUE }, + { "Wave", N_("Wave"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, TRUE }, + { "Phone", N_("Phone"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, TRUE }, + { "CD", N_("CD"), MATE_MIXER_STREAM_CONTROL_ROLE_CD, FALSE, TRUE }, + { "Music", N_("Music"), MATE_MIXER_STREAM_CONTROL_ROLE_MUSIC, FALSE, TRUE }, + { "AC97", N_("AC97"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, TRUE }, + { "LFE", N_("LFE Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, FALSE }, + { "LFE Speaker", N_("LFE Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, FALSE }, + { "Bass Speaker", N_("Bass Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, FALSE }, + { "PC Speaker", N_("PC Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, FALSE, FALSE }, + { "Synth", N_("Synth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, FALSE }, + { "MIDI", N_("MIDI"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, FALSE }, + { "Synth/MIDI", N_("Synth/MIDI"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, FALSE, FALSE }, + { "Bass", N_("Bass"), MATE_MIXER_STREAM_CONTROL_ROLE_BASS, FALSE, FALSE }, + { "Treble", N_("Treble"), MATE_MIXER_STREAM_CONTROL_ROLE_TREBLE, FALSE, FALSE }, + + /* Input controls */ + { "Capture", N_("Capture"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, TRUE, FALSE }, + { "Mic", N_("Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Mic/Line", N_("Microphone/Line In"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, TRUE, FALSE }, + { "Internal Mic", N_("Internal Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Int Mic", N_("Internal Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Front Mic", N_("Front Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Rear Mic", N_("Rear Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Dock Mic", N_("Dock Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Headphone Mic", N_("Headphone Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Headset Mic", N_("Headset Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Inverted Internal Mic", N_("Inverted Internal Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, TRUE, FALSE }, + { "Line", N_("Line In"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, TRUE, FALSE }, + { "Aux", N_("Auxiliary"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, TRUE, FALSE }, + { "Video", N_("Video"), MATE_MIXER_STREAM_CONTROL_ROLE_VIDEO, TRUE, FALSE }, + { "TV Tuner", N_("TV Tuner"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, TRUE, FALSE }, + { "FM", N_("FM"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, TRUE, FALSE }, + { "Mic Boost", N_("Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Mic Boost (+20dB)", N_("Microphone Boost (+20dB)"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Int Mic Boost", N_("Internal Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Internal Mic Boost", N_("Internal Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Front Mic Boost", N_("Front Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Rear Mic Boost", N_("Rear Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Dock Mic Boost", N_("Dock Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Headphone Mic Boost", N_("Headphone Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Headset Mic Boost", N_("Headset Microphone Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { "Line Boost", N_("Line In Boost"), MATE_MIXER_STREAM_CONTROL_ROLE_BOOST, FALSE, FALSE }, + { NULL } +}; + +/* Switches and toggles */ +const AlsaSwitchInfo alsa_switches[] = +{ + { "Analog Output", N_("Analog Output"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Analog Source", N_("Analog Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Capture Source", N_("Capture Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Input Source", N_("Input Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Input Source Select", N_("Input Source Select"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Digital Input Source", N_("Digital Input Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "PCM Capture Source", N_("PCM Capture Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "IEC958 Playback Source", N_("Digital Playback Source"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Mono Output Select", N_("Mono Output Select"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Shared Mic/Line in", N_("Shared Mic/Line In"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Mic Select", N_("Microphone Select"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Mic Jack Mode", N_("Microphone Jack Mode"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Surround Jack Mode", N_("Surround Jack Mode"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + { "Auto-Mute Mode", N_("Auto-Mute Mode"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + + /* (Probably) toggles */ + { "External Amplifier", N_("External Amplifier"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + { "Bass Boost", N_("Bass Boost"), MATE_MIXER_SWITCH_ROLE_BOOST }, + { "Capture Boost", N_("Capture Boost"), MATE_MIXER_SWITCH_ROLE_BOOST }, + { "IEC958", N_("Digital"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "IEC958 In", N_("Digital In"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "IEC958 Optical Raw", N_("Optical"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Auto Gain Control", N_("Auto Gain Control"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + { "Mix", N_("Mix"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + { "Mix Mono", N_("Mix Mono"), MATE_MIXER_SWITCH_ROLE_UNKNOWN }, + { "Mic Capture", N_("Mic Capture"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Input 1", N_("Input 1"), MATE_MIXER_SWITCH_ROLE_PORT }, + { "Input 2", N_("Input 2"), MATE_MIXER_SWITCH_ROLE_PORT }, + { NULL } +}; + +const AlsaSwitchOptionInfo alsa_switch_options[] = +{ + /* Output options */ + { "Speakers", N_("Speakers"), NULL }, + { "Headphones", N_("Headphones"), NULL }, + { "FP Headphones", N_("Front Panel Headphones"), NULL }, + + /* Microphone options */ + { "Mic", N_("Microphone"), "audio-input-microphone" }, + { "Microphone", N_("Microphone"), "audio-input-microphone" }, + { "Mic1", N_("Microphone 1"), "audio-input-microphone" }, + { "Mic2", N_("Microphone 2"), "audio-input-microphone" }, + { "Mic in", N_("Microphone In"), "audio-input-microphone" }, + { "Mic In", N_("Microphone In"), "audio-input-microphone" }, + { "Front Mic", N_("Front Microphone"), "audio-input-microphone" }, + { "Front Microphone", N_("Front Microphone"), "audio-input-microphone" }, + { "Headphone Mic", N_("Headphone Microphone"), "audio-input-microphone" }, + { "Headset Mic", N_("Headset Microphone"), "audio-input-microphone" }, + { "Dock Mic", N_("Dock Microphone"), "audio-input-microphone" }, + { "Internal Mic", N_("Internal Microphone"), "audio-input-microphone" }, + { "Int Mic", N_("Internal Microphone"), "audio-input-microphone" }, + { "Internal Mic 1", N_("Internal Microphone 1"), "audio-input-microphone" }, + { "iMic", N_("Internal Microphone"), "audio-input-microphone" }, + { "i-Mic", N_("Internal Microphone"), "audio-input-microphone" }, + { "IntMic", N_("Internal Microphone"), "audio-input-microphone" }, + { "Int DMic", N_("Internal Digital Microphone"), "audio-input-microphone" }, + { "Digital Mic", N_("Digital Microphone"), "audio-input-microphone" }, + { "Digital Mic 1", N_("Digital Microphone 1"), "audio-input-microphone" }, + { "Digital Mic 2", N_("Digital Microphone 2"), "audio-input-microphone" }, + { "D-Mic", N_("Digital Microphone"), "audio-input-microphone" }, + { "ExtMic", N_("External Microphone"), "audio-input-microphone" }, + { "Ext Mic", N_("External Microphone"), "audio-input-microphone" }, + { "E-Mic", N_("External Microphone"), "audio-input-microphone" }, + { "e-Mic", N_("External Microphone"), "audio-input-microphone" }, + { "Rear Mic", N_("Rear Microphone"), "audio-input-microphone" }, + { "Cam Mic", N_("Camera Microphone"), "audio-input-microphone" }, + + /* Other options */ + { "Analog", N_("Analog"), NULL }, + { "Analog In", N_("Analog In"), NULL }, + { "Analog Inputs", N_("Analog Inputs"), NULL }, + { "Line in", N_("Line In"), NULL }, + { "Line In", N_("Line In"), NULL }, + { "Line-In", N_("Line In"), NULL }, + { "Mic/Line", N_("Microphone/Line In"), NULL }, + { "Line/Mic", N_("Line In/Microphone"), NULL }, + { "LineIn", N_("Line In"), NULL }, + { "Line", N_("Line In"), NULL }, + { "Input1", N_("Input 1"), NULL }, + { "Input2", N_("Input 2"), NULL }, + { "IEC958 In", N_("Digital In"), NULL }, + { "TV Tuner", N_("TV Tuner"), NULL }, + { "FM", N_("FM"), NULL }, + { "AUX", N_("Auxiliary"), NULL }, + { "AUX IN", N_("Auxiliary In"), NULL }, + { "Aux In", N_("Auxiliary In"), NULL }, + { "Aux", N_("Auxiliary"), NULL }, + { "Aux0", N_("Auxiliary 0"), NULL }, + { "Aux1", N_("Auxiliary 1"), NULL }, + { "Aux2", N_("Auxiliary 2"), NULL }, + { "Aux3", N_("Auxiliary 3"), NULL }, + { "Docking-Station", N_("Docking Station"), NULL }, + { "Mixer", N_("Mixer"), NULL }, + { "Unknown1", N_("Unknown 1"), NULL }, + { "Unknown2", N_("Unknown 2"), NULL }, { NULL } }; diff --git a/backends/alsa/alsa-constants.h b/backends/alsa/alsa-constants.h index 81257c7..8137289 100644 --- a/backends/alsa/alsa-constants.h +++ b/backends/alsa/alsa-constants.h @@ -22,14 +22,35 @@ #include <alsa/asoundlib.h> #include <libmatemixer/matemixer.h> +G_BEGIN_DECLS + typedef struct { gchar *name; gchar *label; MateMixerStreamControlRole role; + gboolean use_default_input; + gboolean use_default_output; } AlsaControlInfo; +typedef struct { + gchar *name; + gchar *label; + MateMixerSwitchRole role; +} AlsaSwitchInfo; + +typedef struct { + gchar *name; + gchar *label; + gchar *icon; +} AlsaSwitchOptionInfo; + extern const AlsaControlInfo alsa_controls[]; +extern const AlsaSwitchInfo alsa_switches[]; +extern const AlsaSwitchOptionInfo alsa_switch_options[]; + extern const MateMixerChannelPosition alsa_channel_map_from[]; extern const snd_mixer_selem_channel_id_t alsa_channel_map_to[]; +G_END_DECLS + #endif /* ALSA_CONSTANTS_H */ diff --git a/backends/alsa/alsa-device.c b/backends/alsa/alsa-device.c index 5acc6f5..f7f705e 100644 --- a/backends/alsa/alsa-device.c +++ b/backends/alsa/alsa-device.c @@ -15,7 +15,9 @@ * License along with this library; if not, see <http://www.gnu.org/licenses/>. */ -#include <strings.h> +#include <string.h> +#include <libintl.h> + #include <glib.h> #include <glib/gi18n.h> #include <glib-object.h> @@ -35,6 +37,18 @@ #define ALSA_DEVICE_ICON "audio-card" +#define ALSA_STREAM_CONTROL_GET_SCORE(c) \ + (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (c), \ + "__matemixer_alsa_control_score"))) + +#define ALSA_STREAM_CONTROL_SET_SCORE(c,score) \ + (g_object_set_data (G_OBJECT (c), \ + "__matemixer_alsa_control_score", \ + GINT_TO_POINTER (score))) + +#define ALSA_STREAM_DEFAULT_CONTROL_GET_SCORE(s) \ + (ALSA_STREAM_CONTROL_GET_SCORE (alsa_stream_get_default_control (ALSA_STREAM (s)))) + struct _AlsaDevicePrivate { snd_mixer_t *handle; @@ -43,7 +57,8 @@ struct _AlsaDevicePrivate GCond cond; AlsaStream *input; AlsaStream *output; - GHashTable *switches; + GList *streams; + GList *switches; gboolean events_pending; }; @@ -61,64 +76,83 @@ 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 const GList * alsa_device_list_streams (MateMixerDevice *mmd); +static const 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 GList * alsa_device_list_streams (MateMixerDevice *mmd); -static GList * alsa_device_list_switches (MateMixerDevice *mmd); +static gboolean add_device_switch (AlsaDevice *device, + snd_mixer_elem_t *el); -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_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_switch (AlsaDevice *device, - AlsaStream *stream, - 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 gboolean add_device_switch (AlsaDevice *device, - snd_mixer_elem_t *el); +static void load_element (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 void load_elements_by_name (AlsaDevice *device, + const gchar *name); -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 remove_elements_by_name (AlsaDevice *device, + const gchar *name); -static void load_element (AlsaDevice *device, - snd_mixer_elem_t *el); +static void handle_poll (AlsaDevice *device); -static void load_elements_by_name (AlsaDevice *device, - const gchar *name); +static gboolean handle_process_events (AlsaDevice *device); -static void remove_elements_by_name (AlsaDevice *device, - const gchar *name); +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 handle_poll (AlsaDevice *device); +static void validate_default_controls (AlsaDevice *device); -static gboolean handle_process_events (AlsaDevice *device); +static AlsaStreamControl *get_best_stream_control (AlsaStream *stream); -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 gchar * get_element_name (snd_mixer_elem_t *el); -static void close_device (AlsaDevice *device); +static void get_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role, + gint *score); +static void get_input_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role, + gint *score); +static void get_output_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role, + gint *score); -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, + MateMixerSwitchRole *role); -static void get_switch_info (snd_mixer_elem_t *el, - gchar **name, - gchar **label); +static void close_mixer (AlsaDevice *device); + +static void free_stream_list (AlsaDevice *device); + +static gint compare_switch_name (gconstpointer a, + gconstpointer b); static void alsa_device_class_init (AlsaDeviceClass *klass) @@ -131,14 +165,13 @@ alsa_device_class_init (AlsaDeviceClass *klass) 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_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (AlsaDeviceClass, closed), NULL, NULL, @@ -157,11 +190,6 @@ alsa_device_init (AlsaDevice *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); @@ -178,7 +206,12 @@ alsa_device_dispose (GObject *object) g_clear_object (&device->priv->input); g_clear_object (&device->priv->output); - g_hash_table_remove_all (device->priv->switches); + if (device->priv->switches != NULL) { + g_list_free_full (device->priv->switches, g_object_unref); + device->priv->switches = NULL; + } + + free_stream_list (device); G_OBJECT_CLASS (alsa_device_parent_class)->dispose (object); } @@ -193,11 +226,9 @@ alsa_device_finalize (GObject *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); + close_mixer (device); G_OBJECT_CLASS (alsa_device_parent_class)->dispose (object); } @@ -208,7 +239,7 @@ 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 (name != NULL, NULL); g_return_val_if_fail (label != NULL, NULL); device = g_object_new (ALSA_TYPE_DEVICE, @@ -223,13 +254,13 @@ alsa_device_new (const gchar *name, const gchar *label) 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); + MATE_MIXER_DIRECTION_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); + MATE_MIXER_DIRECTION_OUTPUT); g_free (stream_name); return device; @@ -289,6 +320,72 @@ alsa_device_open (AlsaDevice *device) return TRUE; } +gboolean +alsa_device_is_open (AlsaDevice *device) +{ + g_return_val_if_fail (ALSA_IS_DEVICE (device), FALSE); + + if (device->priv->handle != NULL) + return TRUE; + + return FALSE; +} + +void +alsa_device_close (AlsaDevice *device) +{ + GList *list; + + g_return_if_fail (ALSA_IS_DEVICE (device)); + + if (device->priv->handle == NULL) + return; + + /* Make each stream remove its controls and switches */ + if (alsa_stream_has_controls_or_switches (device->priv->input) == TRUE) { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->input)); + + alsa_stream_remove_all (device->priv->input); + free_stream_list (device); + + g_signal_emit_by_name (G_OBJECT (device), + "stream-removed", + name); + } + + if (alsa_stream_has_controls_or_switches (device->priv->output) == TRUE) { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->output)); + + alsa_stream_remove_all (device->priv->output); + free_stream_list (device); + + g_signal_emit_by_name (G_OBJECT (device), + "stream-removed", + name); + } + + /* Remove device switches */ + list = device->priv->switches; + while (list != NULL) { + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (list->data); + GList *next = list->next; + + device->priv->switches = g_list_delete_link (device->priv->switches, list); + g_signal_emit_by_name (G_OBJECT (device), + "switch-removed", + mate_mixer_switch_get_name (swtch)); + g_object_unref (swtch); + + list = next; + } + + close_mixer (device); + + g_signal_emit (G_OBJECT (device), signals[CLOSED], 0); +} + void alsa_device_load (AlsaDevice *device) { @@ -307,6 +404,9 @@ alsa_device_load (AlsaDevice *device) el = snd_mixer_elem_next (el); } + /* Assign proper default controls */ + validate_default_controls (device); + /* Set callback for ALSA events */ snd_mixer_set_callback (device->priv->handle, handle_callback); snd_mixer_set_callback_private (device->priv->handle, device); @@ -332,7 +432,7 @@ alsa_device_get_input_stream (AlsaDevice *device) /* Normally controlless streams should not exist, here we simulate the * behaviour for the owning instance */ - if (alsa_stream_is_empty (device->priv->input) == FALSE) + if (alsa_stream_has_controls_or_switches (device->priv->input) == TRUE) return device->priv->input; return NULL; @@ -345,7 +445,7 @@ alsa_device_get_output_stream (AlsaDevice *device) /* Normally controlless streams should not exist, here we simulate the * behaviour for the owning instance */ - if (alsa_stream_is_empty (device->priv->output) == FALSE) + if (alsa_stream_has_controls_or_switches (device->priv->output) == TRUE) return device->priv->output; return NULL; @@ -360,44 +460,39 @@ add_element (AlsaDevice *device, AlsaStream *stream, AlsaElement *element) return FALSE; if (stream != NULL) { - gboolean empty = FALSE; - - if (alsa_stream_is_empty (stream) == TRUE) { + if (alsa_stream_has_controls_or_switches (stream) == FALSE) { const gchar *name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)); + free_stream_list (device); + /* 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)) { + /* Stream control */ 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_TOGGLE (element)) { + /* Toggle belonging to a stream */ + alsa_stream_add_toggle (stream, ALSA_TOGGLE (element)); + added = TRUE; } - } else if (ALSA_IS_SWITCH (element)) { + } else if (ALSA_IS_SWITCH (element) || ALSA_IS_TOGGLE (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)); + device->priv->switches = + g_list_append (device->priv->switches, g_object_ref (element)); g_signal_emit_by_name (G_OBJECT (device), "switch-added", @@ -414,47 +509,32 @@ add_element (AlsaDevice *device, AlsaStream *stream, AlsaElement *element) 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 * +static const 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; + if (device->priv->streams == NULL) { + if (device->priv->output != NULL) + device->priv->streams = g_list_prepend (device->priv->streams, + g_object_ref (device->priv->output)); + if (device->priv->input != NULL) + device->priv->streams = g_list_prepend (device->priv->streams, + g_object_ref (device->priv->input)); + } + return device->priv->streams; } -static GList * +static const 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; + return ALSA_DEVICE (mmd)->priv->switches; } static gboolean @@ -463,25 +543,28 @@ add_stream_input_control (AlsaDevice *device, snd_mixer_elem_t *el) AlsaStreamControl *control; gchar *name; gchar *label; + gboolean ret; + gint score; MateMixerStreamControlRole role; - get_control_info (el, &name, &label, &role); + get_input_control_info (el, &name, &label, &role, &score); - g_debug ("Found device %s input control %s", + g_debug ("Reading device %s input control %s", mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), - label); + name); - control = alsa_stream_input_control_new (name, label, role); + control = alsa_stream_input_control_new (name, label, role, device->priv->input); g_free (name); g_free (label); + ALSA_STREAM_CONTROL_SET_SCORE (control, score); + 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; + ret = add_element (device, device->priv->input, ALSA_ELEMENT (control)); + + g_object_unref (control); + return ret; } static gboolean @@ -490,49 +573,51 @@ add_stream_output_control (AlsaDevice *device, snd_mixer_elem_t *el) AlsaStreamControl *control; gchar *label; gchar *name; + gboolean ret; + gint score; MateMixerStreamControlRole role; - get_control_info (el, &name, &label, &role); + get_output_control_info (el, &name, &label, &role, &score); - g_debug ("Found device %s output control %s", + g_debug ("Reading device %s output control %s", mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), - label); + name); - control = alsa_stream_output_control_new (name, label, role); + control = alsa_stream_output_control_new (name, label, role, device->priv->output); g_free (name); g_free (label); + ALSA_STREAM_CONTROL_SET_SCORE (control, score); + 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; + ret = add_element (device, device->priv->output, ALSA_ELEMENT (control)); + + g_object_unref (control); + return ret; } static AlsaToggle * create_toggle (AlsaDevice *device, snd_mixer_elem_t *el, AlsaToggleType type) { - AlsaToggle *toggle; - AlsaSwitchOption *on; - AlsaSwitchOption *off; - gchar *name; - gchar *label; + AlsaToggle *toggle; + AlsaSwitchOption *on; + AlsaSwitchOption *off; + gchar *name; + gchar *label; + MateMixerSwitchRole role; 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); + get_switch_info (el, &name, &label, &role); + + toggle = alsa_toggle_new (name, label, role, type, on, off); alsa_element_set_snd_element (ALSA_ELEMENT (toggle), el); + g_free (name); + g_free (label); g_object_unref (on); g_object_unref (off); @@ -542,14 +627,15 @@ create_toggle (AlsaDevice *device, snd_mixer_elem_t *el, AlsaToggleType type) 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; + AlsaElement *element = NULL; + GList *options = NULL; + gchar *name; + gchar *label; + gchar item[128]; + guint i; + gint count; + gboolean ret; + MateMixerSwitchRole role; count = snd_mixer_selem_get_enum_items (el); if G_UNLIKELY (count <= 0) { @@ -560,30 +646,44 @@ add_switch (AlsaDevice *device, AlsaStream *stream, snd_mixer_elem_t *el) } 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 + gint ret = snd_mixer_selem_get_enum_item_name (el, i, sizeof (item), item); + + if G_LIKELY (ret == 0) { + gint j; + AlsaSwitchOption *option = NULL; + + for (j = 0; alsa_switch_options[j].name != NULL; j++) + if (strcmp (item, alsa_switch_options[j].name) == 0) { + option = alsa_switch_option_new (item, + gettext (alsa_switch_options[j].label), + alsa_switch_options[j].icon, + i); + break; + } + + if (option == NULL) + option = alsa_switch_option_new (item, item, NULL, i); + + options = g_list_prepend (options, option); + } else g_warning ("Failed to read switch item name: %s", snd_strerror (ret)); } - get_switch_info (el, &name, &label); + get_switch_info (el, &name, &label, &role); /* Takes ownership of options */ - element = ALSA_ELEMENT (alsa_switch_new (name, label, g_list_reverse (options))); + element = ALSA_ELEMENT (alsa_switch_new (name, label, + role, + 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; + ret = add_element (device, stream, element); + + g_object_unref (element); + return ret; } static gboolean @@ -623,6 +723,7 @@ static gboolean add_stream_input_toggle (AlsaDevice *device, snd_mixer_elem_t *el) { AlsaToggle *toggle; + gboolean ret; g_debug ("Reading device %s input toggle %s", mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), @@ -630,17 +731,17 @@ add_stream_input_toggle (AlsaDevice *device, snd_mixer_elem_t *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; + ret = add_element (device, device->priv->input, ALSA_ELEMENT (toggle)); + + g_object_unref (toggle); + return ret; } static gboolean add_stream_output_toggle (AlsaDevice *device, snd_mixer_elem_t *el) { AlsaToggle *toggle; + gboolean ret; g_debug ("Reading device %s output toggle %s", mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)), @@ -648,11 +749,10 @@ add_stream_output_toggle (AlsaDevice *device, snd_mixer_elem_t *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; + ret = add_element (device, device->priv->output, ALSA_ELEMENT (toggle)); + + g_object_unref (toggle); + return ret; } static void @@ -671,8 +771,7 @@ load_element (AlsaDevice *device, snd_mixer_elem_t *el) 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 */ + * are considered to be a part of the whole device */ if (cenum == FALSE && penum == FALSE) { add_device_switch (device, el); } @@ -707,25 +806,28 @@ load_element (AlsaDevice *device, snd_mixer_elem_t *el) static void load_elements_by_name (AlsaDevice *device, const gchar *name) { - AlsaElement *element; + GList *item; 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); + item = g_list_find_custom (device->priv->switches, name, compare_switch_name); + if (item != NULL) + alsa_element_load (ALSA_ELEMENT (item->data)); } static void remove_elements_by_name (AlsaDevice *device, const gchar *name) { + GList *item; + 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) { + if (alsa_stream_has_controls_or_switches (device->priv->input) == FALSE) { const gchar *stream_name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->input)); + free_stream_list (device); g_signal_emit_by_name (G_OBJECT (device), "stream-removed", stream_name); @@ -734,20 +836,28 @@ remove_elements_by_name (AlsaDevice *device, const gchar *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) { + if (alsa_stream_has_controls_or_switches (device->priv->output) == FALSE) { const gchar *stream_name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->output)); + free_stream_list (device); g_signal_emit_by_name (G_OBJECT (device), "stream-removed", stream_name); } } - if (g_hash_table_remove (device->priv->switches, name) == TRUE) + item = g_list_find_custom (device->priv->switches, name, compare_switch_name); + if (item != NULL) { + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (item->data); + + device->priv->switches = g_list_delete_link (device->priv->switches, item); g_signal_emit_by_name (G_OBJECT (device), "switch-removed", - name); + mate_mixer_switch_get_name (swtch)); + + g_object_unref (swtch); + } } static void @@ -806,7 +916,7 @@ handle_process_events (AlsaDevice *device) if (device->priv->handle != NULL) { gint ret = snd_mixer_handle_events (device->priv->handle); if (ret < 0) - close_device (device); + alsa_device_close (device); } device->priv->events_pending = FALSE; @@ -826,7 +936,15 @@ 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); + if (device->priv->handle == NULL) { + /* The mixer is already closed */ + return 0; + } + load_element (device, el); + + /* Revalidate default controls assignment */ + validate_default_controls (device); } return 0; } @@ -838,6 +956,11 @@ handle_element_callback (snd_mixer_elem_t *el, guint mask) gchar *name; device = snd_mixer_elem_get_callback_private (el); + if (device->priv->handle == NULL) { + /* The mixer is already closed */ + return 0; + } + name = get_element_name (el); if (mask == SND_CTL_EVENT_MASK_REMOVE) { @@ -846,10 +969,16 @@ handle_element_callback (snd_mixer_elem_t *el, guint mask) snd_mixer_elem_set_callback (el, NULL); remove_elements_by_name (device, name); + + /* Revalidate default controls assignment */ + validate_default_controls (device); } else { if (mask & SND_CTL_EVENT_MASK_INFO) { remove_elements_by_name (device, name); load_element (device, el); + + /* Revalidate default controls assignment */ + validate_default_controls (device); } if (mask & SND_CTL_EVENT_MASK_VALUE) load_elements_by_name (device, name); @@ -860,16 +989,86 @@ handle_element_callback (snd_mixer_elem_t *el, guint mask) } static void -close_device (AlsaDevice *device) +validate_default_controls (AlsaDevice *device) { - if (device->priv->handle != NULL) { - snd_mixer_close (device->priv->handle); - device->priv->handle = NULL; + AlsaStreamControl *best; + gint best_score; + gint current_score; + + /* + * Select the most suitable default control. Don't try too hard here because + * our list of known elements is incomplete and most drivers seem to provide + * the list in a reasonable order with the best element at the start. Each + * element in our list has a value (or score) which is simply its position + * in the list. Better elements are on the top, so smaller value represents + * a better element. + * + * Two cases are handled here: + * 1) The current default control is in our list, but the list also includes + * a better element. + * 2) The current default control is not in our list, but the list includes + * an element which is reasonably good. + * + * In other cases just keep the first control as the default. + */ + if (alsa_stream_has_controls (device->priv->input) == TRUE) { + best = get_best_stream_control (device->priv->input); + + best_score = ALSA_STREAM_CONTROL_GET_SCORE (best); + current_score = ALSA_STREAM_DEFAULT_CONTROL_GET_SCORE (device->priv->input); + + /* See if the best element would make a good default one */ + if (best_score > -1) { + g_debug ("Found usable default input element %s (score %d)", + mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (best)), + best_score); + + if (current_score == -1 || best_score < current_score) + alsa_stream_set_default_control (device->priv->input, best); + } } - /* 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); + if (alsa_stream_has_controls (device->priv->output) == TRUE) { + best = get_best_stream_control (device->priv->output); + + best_score = ALSA_STREAM_CONTROL_GET_SCORE (best); + current_score = ALSA_STREAM_DEFAULT_CONTROL_GET_SCORE (device->priv->output); + + /* See if the best element would make a good default one */ + if (best_score > -1) { + g_debug ("Found usable default output element %s (score %d)", + mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (best)), + best_score); + + if (current_score == -1 || best_score < current_score) + alsa_stream_set_default_control (device->priv->output, best); + } + } +} + +static AlsaStreamControl * +get_best_stream_control (AlsaStream *stream) +{ + const GList *list; + AlsaStreamControl *best = NULL; + guint best_score = -1; + + list = mate_mixer_stream_list_controls (MATE_MIXER_STREAM (stream)); + while (list != NULL) { + AlsaStreamControl *current; + guint current_score; + + current = ALSA_STREAM_CONTROL (list->data); + current_score = ALSA_STREAM_CONTROL_GET_SCORE (current); + + if (best == NULL || (current_score != -1 && + (best_score == -1 || current_score < best_score))) { + best = current; + best_score = current_score; + } + list = list->next; + } + return best; } static gchar * @@ -884,7 +1083,8 @@ static void get_control_info (snd_mixer_elem_t *el, gchar **name, gchar **label, - MateMixerStreamControlRole *role) + MateMixerStreamControlRole *role, + gint *score) { MateMixerStreamControlRole r = MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN; const gchar *n; @@ -893,49 +1093,119 @@ get_control_info (snd_mixer_elem_t *el, 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; - } + for (i = 0; alsa_controls[i].name != NULL; i++) { + if (strcmp (n, alsa_controls[i].name) != 0) + continue; + + l = gettext (alsa_controls[i].label); + r = alsa_controls[i].role; + break; + } *name = get_element_name (el); - if (l != NULL) + if (l != NULL) { *label = g_strdup (l); - else + *score = i; + } else { *label = g_strdup (n); + *score = -1; + } *role = r; } static void -get_switch_info (snd_mixer_elem_t *el, - gchar **name, - gchar **label) +get_input_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role, + gint *score) { - // MateMixerStreamControlRole r = MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN; - const gchar *n; - const gchar *l = NULL; - // gint i; + get_control_info (el, name, label, role, score); + + if (*score > -1 && alsa_controls[*score].use_default_input == FALSE) + *score = -1; +} + +static void +get_output_control_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerStreamControlRole *role, + gint *score) +{ + get_control_info (el, name, label, role, score); + + if (*score > -1 && alsa_controls[*score].use_default_output == FALSE) + *score = -1; +} + +static void +get_switch_info (snd_mixer_elem_t *el, + gchar **name, + gchar **label, + MateMixerSwitchRole *role) +{ + MateMixerSwitchRole r = MATE_MIXER_SWITCH_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_switches[i].name != NULL; i++) { + if (strcmp (n, alsa_switches[i].name) != 0) + continue; + + l = gettext (alsa_switches[i].label); + r = alsa_switches[i].role; + break; + } -/* - 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; + *role = r; +} + +static void +close_mixer (AlsaDevice *device) +{ + snd_mixer_t *handle; + + if (device->priv->handle == NULL) + return; + + /* Closing the mixer may fire up remove callbacks, prevent this by unsetting + * the handle before closing it and checking it in the callback. + * Ideally, we should unset callbacks from all the elements, but this seems + * to do the job. */ + handle = device->priv->handle; + + device->priv->handle = NULL; + snd_mixer_close (handle); +} + +static void +free_stream_list (AlsaDevice *device) +{ + /* This function is called each time the stream list changes */ + if (device->priv->streams == NULL) + return; + + g_list_free_full (device->priv->streams, g_object_unref); + + device->priv->streams = NULL; +} + +static gint +compare_switch_name (gconstpointer a, gconstpointer b) +{ + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (a); + const gchar *name = (const gchar *) b; + + return strcmp (mate_mixer_switch_get_name (swtch), name); } diff --git a/backends/alsa/alsa-device.h b/backends/alsa/alsa-device.h index 3b3c970..9e908cf 100644 --- a/backends/alsa/alsa-device.h +++ b/backends/alsa/alsa-device.h @@ -20,6 +20,7 @@ #include <glib.h> #include <glib-object.h> +#include <libmatemixer/matemixer.h> #include "alsa-stream.h" @@ -64,6 +65,9 @@ AlsaDevice *alsa_device_new (const gchar *name, const gchar *label); gboolean alsa_device_open (AlsaDevice *device); +gboolean alsa_device_is_open (AlsaDevice *device); +void alsa_device_close (AlsaDevice *device); + void alsa_device_load (AlsaDevice *device); AlsaStream *alsa_device_get_input_stream (AlsaDevice *device); diff --git a/backends/alsa/alsa-element.c b/backends/alsa/alsa-element.c index f925064..d837965 100644 --- a/backends/alsa/alsa-element.c +++ b/backends/alsa/alsa-element.c @@ -51,3 +51,19 @@ alsa_element_load (AlsaElement *element) return ALSA_ELEMENT_GET_INTERFACE (element)->load (element); } + +void +alsa_element_close (AlsaElement *element) +{ + AlsaElementInterface *iface; + + g_return_if_fail (ALSA_IS_ELEMENT (element)); + + /* Close the element by unsetting the ALSA element and optionally calling + * a closing function */ + alsa_element_set_snd_element (element, NULL); + + iface = ALSA_ELEMENT_GET_INTERFACE (element); + if (iface->close != NULL) + iface->close (element); +} diff --git a/backends/alsa/alsa-element.h b/backends/alsa/alsa-element.h index 01d30f1..1c30f68 100644 --- a/backends/alsa/alsa-element.h +++ b/backends/alsa/alsa-element.h @@ -46,6 +46,7 @@ struct _AlsaElementInterface snd_mixer_elem_t *el); gboolean (*load) (AlsaElement *element); + void (*close) (AlsaElement *element); }; GType alsa_element_get_type (void) G_GNUC_CONST; @@ -56,6 +57,8 @@ void alsa_element_set_snd_element (AlsaElement *element, gboolean alsa_element_load (AlsaElement *element); +void alsa_element_close (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 index bc7a937..97a0f8b 100644 --- a/backends/alsa/alsa-stream-control.c +++ b/backends/alsa/alsa-stream-control.c @@ -18,6 +18,7 @@ #include <glib.h> #include <glib-object.h> #include <alsa/asoundlib.h> + #include <libmatemixer/matemixer.h> #include <libmatemixer/matemixer-private.h> @@ -159,64 +160,67 @@ alsa_stream_control_set_data (AlsaStreamControl *control, AlsaControlData *data) { MateMixerStreamControlFlags flags = MATE_MIXER_STREAM_CONTROL_NO_FLAGS; MateMixerStreamControl *mmsc; + gboolean mute = FALSE; g_return_if_fail (ALSA_IS_STREAM_CONTROL (control)); g_return_if_fail (data != NULL); mmsc = MATE_MIXER_STREAM_CONTROL (control); + control->priv->data = *data; + g_object_freeze_notify (G_OBJECT (control)); if (data->channels > 0) { if (data->switch_usable == TRUE) { - flags |= MATE_MIXER_STREAM_CONTROL_HAS_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; + } + } + + flags |= MATE_MIXER_STREAM_CONTROL_MUTE_READABLE; if (data->active == TRUE) - flags |= MATE_MIXER_STREAM_CONTROL_CAN_SET_MUTE; + flags |= MATE_MIXER_STREAM_CONTROL_MUTE_WRITABLE; } - flags |= MATE_MIXER_STREAM_CONTROL_HAS_VOLUME; + + flags |= MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE; 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; + flags |= MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE; - control->priv->data = *data; - control->priv->channel_mask = _mate_mixer_create_channel_mask (data->c, data->channels); + if (data->max_decibel > -MATE_MIXER_INFINITY) + flags |= MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL; - 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; + 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; + } - 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; + g_object_notify (G_OBJECT (control), "volume"); + } else { + control->priv->channel_mask = 0; } + _mate_mixer_stream_control_set_mute (mmsc, mute); _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) @@ -237,7 +241,6 @@ 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; } @@ -288,6 +291,8 @@ alsa_stream_control_set_mute (MateMixerStreamControl *mmsc, gboolean mute) for (i = 0; i < control->priv->data.channels; i++) control->priv->data.m[i] = mute; + + _mate_mixer_stream_control_set_mute (mmsc, mute); } return TRUE; } @@ -344,6 +349,8 @@ alsa_stream_control_set_volume (MateMixerStreamControl *mmsc, guint volume) for (i = 0; i < control->priv->data.channels; i++) control->priv->data.v[i] = volume; + control->priv->data.volume = volume; + g_object_notify (G_OBJECT (control), "volume"); } return TRUE; @@ -364,7 +371,7 @@ alsa_stream_control_get_decibel (MateMixerStreamControl *mmsc) volume = alsa_stream_control_get_volume (mmsc); if (klass->get_decibel_from_volume (control, volume, &decibel) == FALSE) - return FALSE; + return -MATE_MIXER_INFINITY; return decibel; } @@ -428,7 +435,7 @@ alsa_stream_control_get_channel_volume (MateMixerStreamControl *mmsc, guint chan control = ALSA_STREAM_CONTROL (mmsc); if (channel >= control->priv->data.channels) - return FALSE; + return 0; return control->priv->data.v[channel]; } @@ -463,6 +470,7 @@ alsa_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, guint chan if (klass->set_channel_volume (control, c, volume) == FALSE) return FALSE; + // XXX recalc total volume control->priv->data.v[channel] = volume; g_object_notify (G_OBJECT (control), "volume"); @@ -483,13 +491,13 @@ alsa_stream_control_get_channel_decibel (MateMixerStreamControl *mmsc, guint cha control = ALSA_STREAM_CONTROL (mmsc); if (channel >= control->priv->data.channels) - return FALSE; + return -MATE_MIXER_INFINITY; 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 -MATE_MIXER_INFINITY; return decibel; } @@ -637,27 +645,27 @@ alsa_stream_control_set_fade (MateMixerStreamControl *mmsc, gfloat fade) } static guint -alsa_stream_control_get_min_volume (MateMixerStreamControl *msc) +alsa_stream_control_get_min_volume (MateMixerStreamControl *mmsc) { - return ALSA_STREAM_CONTROL (msc)->priv->data.min; + return ALSA_STREAM_CONTROL (mmsc)->priv->data.min; } static guint -alsa_stream_control_get_max_volume (MateMixerStreamControl *msc) +alsa_stream_control_get_max_volume (MateMixerStreamControl *mmsc) { - return ALSA_STREAM_CONTROL (msc)->priv->data.max; + return ALSA_STREAM_CONTROL (mmsc)->priv->data.max; } static guint -alsa_stream_control_get_normal_volume (MateMixerStreamControl *msc) +alsa_stream_control_get_normal_volume (MateMixerStreamControl *mmsc) { - return ALSA_STREAM_CONTROL (msc)->priv->data.max; + return ALSA_STREAM_CONTROL (mmsc)->priv->data.max; } static guint -alsa_stream_control_get_base_volume (MateMixerStreamControl *msc) +alsa_stream_control_get_base_volume (MateMixerStreamControl *mmsc) { - return ALSA_STREAM_CONTROL (msc)->priv->data.max; + return ALSA_STREAM_CONTROL (mmsc)->priv->data.max; } static void diff --git a/backends/alsa/alsa-stream-control.h b/backends/alsa/alsa-stream-control.h index f9ac6b6..acd02bd 100644 --- a/backends/alsa/alsa-stream-control.h +++ b/backends/alsa/alsa-stream-control.h @@ -41,9 +41,6 @@ typedef struct { 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) \ @@ -103,9 +100,6 @@ AlsaControlData * alsa_stream_control_get_data (AlsaStreamControl 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 index 2ef0c42..2e3f46d 100644 --- a/backends/alsa/alsa-stream-input-control.c +++ b/backends/alsa/alsa-stream-input-control.c @@ -22,6 +22,7 @@ #include <libmatemixer/matemixer.h> #include <libmatemixer/matemixer-private.h> +#include "alsa-constants.h" #include "alsa-element.h" #include "alsa-stream-control.h" #include "alsa-stream-input-control.h" @@ -51,8 +52,7 @@ static gboolean alsa_stream_input_control_get_decibel_from_volume (AlsaStreamCon guint volume, gdouble *decibel); -static void read_volume_data (snd_mixer_elem_t *el, - AlsaControlData *data); +static void read_volume_data (snd_mixer_elem_t *el, AlsaControlData *data); static void alsa_stream_input_control_class_init (AlsaStreamInputControlClass *klass) @@ -77,12 +77,14 @@ alsa_stream_input_control_init (AlsaStreamInputControl *control) AlsaStreamControl * alsa_stream_input_control_new (const gchar *name, const gchar *label, - MateMixerStreamControlRole role) + MateMixerStreamControlRole role, + AlsaStream *stream) { return g_object_new (ALSA_TYPE_STREAM_INPUT_CONTROL, "name", name, "label", label, "role", role, + "stream", stream, NULL); } @@ -98,7 +100,6 @@ alsa_stream_input_control_load (AlsaStreamControl *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 (); @@ -180,8 +181,7 @@ alsa_stream_input_control_set_channel_volume (AlsaStreamControl *contr 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 */ + /* Set the volume for a single channel */ ret = snd_mixer_selem_set_capture_volume (el, channel, volume); if (ret < 0) { g_warning ("Failed to set channel volume: %s", snd_strerror (ret)); diff --git a/backends/alsa/alsa-stream-input-control.h b/backends/alsa/alsa-stream-input-control.h index c427e3c..0ce885a 100644 --- a/backends/alsa/alsa-stream-input-control.h +++ b/backends/alsa/alsa-stream-input-control.h @@ -22,6 +22,7 @@ #include <glib-object.h> #include <libmatemixer/matemixer.h> +#include "alsa-stream.h" #include "alsa-stream-control.h" G_BEGIN_DECLS @@ -57,7 +58,8 @@ 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); + MateMixerStreamControlRole role, + AlsaStream *stream); G_END_DECLS diff --git a/backends/alsa/alsa-stream-output-control.c b/backends/alsa/alsa-stream-output-control.c index 5a3e6b3..1f4faf8 100644 --- a/backends/alsa/alsa-stream-output-control.c +++ b/backends/alsa/alsa-stream-output-control.c @@ -22,6 +22,7 @@ #include <libmatemixer/matemixer.h> #include <libmatemixer/matemixer-private.h> +#include "alsa-constants.h" #include "alsa-element.h" #include "alsa-stream-control.h" #include "alsa-stream-output-control.h" @@ -51,8 +52,7 @@ static gboolean alsa_stream_output_control_get_decibel_from_volume (AlsaStreamCo guint volume, gdouble *decibel); -static void read_volume_data (snd_mixer_elem_t *el, - AlsaControlData *data); +static void read_volume_data (snd_mixer_elem_t *el, AlsaControlData *data); static void alsa_stream_output_control_class_init (AlsaStreamOutputControlClass *klass) @@ -77,12 +77,14 @@ alsa_stream_output_control_init (AlsaStreamOutputControl *control) AlsaStreamControl * alsa_stream_output_control_new (const gchar *name, const gchar *label, - MateMixerStreamControlRole role) + MateMixerStreamControlRole role, + AlsaStream *stream) { return g_object_new (ALSA_TYPE_STREAM_OUTPUT_CONTROL, "name", name, "label", label, "role", role, + "stream", stream, NULL); } @@ -98,7 +100,6 @@ alsa_stream_output_control_load (AlsaStreamControl *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 (); @@ -180,8 +181,7 @@ alsa_stream_output_control_set_channel_volume (AlsaStreamControl *cont 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 */ + /* Set the volume for a single channel */ ret = snd_mixer_selem_set_playback_volume (el, channel, volume); if (ret < 0) { g_warning ("Failed to set channel volume: %s", snd_strerror (ret)); diff --git a/backends/alsa/alsa-stream-output-control.h b/backends/alsa/alsa-stream-output-control.h index 845eaae..9a5b708 100644 --- a/backends/alsa/alsa-stream-output-control.h +++ b/backends/alsa/alsa-stream-output-control.h @@ -22,6 +22,7 @@ #include <glib-object.h> #include <libmatemixer/matemixer.h> +#include "alsa-stream.h" #include "alsa-stream-control.h" G_BEGIN_DECLS @@ -57,7 +58,8 @@ 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); + MateMixerStreamControlRole role, + AlsaStream *stream); G_END_DECLS diff --git a/backends/alsa/alsa-stream.c b/backends/alsa/alsa-stream.c index d2f68d4..bc9c1b5 100644 --- a/backends/alsa/alsa-stream.c +++ b/backends/alsa/alsa-stream.c @@ -18,6 +18,7 @@ #include <glib.h> #include <glib-object.h> #include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> #include "alsa-element.h" #include "alsa-stream.h" @@ -26,27 +27,23 @@ struct _AlsaStreamPrivate { - GHashTable *switches; - GHashTable *controls; - MateMixerStreamControl *control; + GList *switches; + GList *controls; }; 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 const GList *alsa_stream_list_controls (MateMixerStream *mms); +static const GList *alsa_stream_list_switches (MateMixerStream *mms); -static GList * alsa_stream_list_controls (MateMixerStream *mms); -static GList * alsa_stream_list_switches (MateMixerStream *mms); +static gint compare_control_name (gconstpointer a, + gconstpointer b); +static gint compare_switch_name (gconstpointer a, + gconstpointer b); static void alsa_stream_class_init (AlsaStreamClass *klass) @@ -55,15 +52,11 @@ alsa_stream_class_init (AlsaStreamClass *klass) MateMixerStreamClass *stream_class; object_class = G_OBJECT_CLASS (klass); - object_class->dispose = alsa_stream_dispose; - object_class->finalize = alsa_stream_finalize; + object_class->dispose = alsa_stream_dispose; 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; + 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)); } @@ -74,16 +67,6 @@ 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 @@ -93,36 +76,30 @@ alsa_stream_dispose (GObject *object) 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); + if (stream->priv->controls != NULL) { + g_list_free_full (stream->priv->controls, g_object_unref); + stream->priv->controls = NULL; + } + if (stream->priv->switches != NULL) { + g_list_free_full (stream->priv->switches, g_object_unref); + stream->priv->switches = NULL; + } 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) +alsa_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerDirection direction) { + const gchar *label = mate_mixer_device_get_label (device); + return g_object_new (ALSA_TYPE_STREAM, "name", name, + "label", label, "device", device, - "flags", flags, + "direction", direction, NULL); } @@ -135,9 +112,16 @@ alsa_stream_add_control (AlsaStream *stream, AlsaStreamControl *control) 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)); + + stream->priv->controls = + g_list_append (stream->priv->controls, g_object_ref (control)); + + g_signal_emit_by_name (G_OBJECT (stream), + "control-added", + name); + + if (alsa_stream_has_default_control (stream) == FALSE) + alsa_stream_set_default_control (stream, control); } void @@ -149,125 +133,250 @@ alsa_stream_add_switch (AlsaStream *stream, AlsaSwitch *swtch) 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)); + + stream->priv->switches = + g_list_append (stream->priv->switches, g_object_ref (swtch)); + + g_signal_emit_by_name (G_OBJECT (stream), + "switch-added", + name); +} + +void +alsa_stream_add_toggle (AlsaStream *stream, AlsaToggle *toggle) +{ + const gchar *name; + + g_return_if_fail (ALSA_IS_STREAM (stream)); + g_return_if_fail (ALSA_IS_TOGGLE (toggle)); + + name = mate_mixer_switch_get_name (MATE_MIXER_SWITCH (toggle)); + + /* Toggle is MateMixerSwitch, but not AlsaSwitch */ + stream->priv->switches = + g_list_append (stream->priv->switches, g_object_ref (toggle)); + + g_signal_emit_by_name (G_OBJECT (stream), + "switch-added", + name); +} + +gboolean +alsa_stream_has_controls (AlsaStream *stream) +{ + g_return_val_if_fail (ALSA_IS_STREAM (stream), FALSE); + + if (stream->priv->controls != NULL) + return TRUE; + + return FALSE; } gboolean -alsa_stream_is_empty (AlsaStream *stream) +alsa_stream_has_switches (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; + if (stream->priv->switches != NULL) + return TRUE; - return TRUE; + return FALSE; +} + +gboolean +alsa_stream_has_controls_or_switches (AlsaStream *stream) +{ + g_return_val_if_fail (ALSA_IS_STREAM (stream), FALSE); + + if (stream->priv->controls != NULL || + stream->priv->switches != NULL) + return TRUE; + + return FALSE; +} + +gboolean +alsa_stream_has_default_control (AlsaStream *stream) +{ + g_return_val_if_fail (ALSA_IS_STREAM (stream), FALSE); + + if (mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream)) != NULL) + return TRUE; + + return FALSE; +} + +AlsaStreamControl * +alsa_stream_get_default_control (AlsaStream *stream) +{ + MateMixerStreamControl *control; + + g_return_val_if_fail (ALSA_IS_STREAM (stream), NULL); + + control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream)); + if (control != NULL) + return ALSA_STREAM_CONTROL (control); + + return NULL; } 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); + g_return_if_fail (control == NULL || ALSA_IS_STREAM_CONTROL (control)); - if (control != NULL) - stream->priv->control = MATE_MIXER_STREAM_CONTROL (g_object_ref (control)); + if (control == NULL) + _mate_mixer_stream_set_default_control (MATE_MIXER_STREAM (stream), NULL); else - stream->priv->control = NULL; + _mate_mixer_stream_set_default_control (MATE_MIXER_STREAM (stream), + MATE_MIXER_STREAM_CONTROL (control)); } void alsa_stream_load_elements (AlsaStream *stream, const gchar *name) { - AlsaElement *element; + GList *item; 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); + item = g_list_find_custom (stream->priv->controls, name, compare_control_name); + if (item != NULL) + alsa_element_load (ALSA_ELEMENT (item->data)); - element = g_hash_table_lookup (stream->priv->switches, name); - if (element != NULL) - alsa_element_load (element); + item = g_list_find_custom (stream->priv->switches, name, compare_switch_name); + if (item != NULL) + alsa_element_load (ALSA_ELEMENT (item->data)); } gboolean alsa_stream_remove_elements (AlsaStream *stream, const gchar *name) { + GList *item; 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) + item = g_list_find_custom (stream->priv->controls, name, compare_control_name); + if (item != NULL) { + MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (item->data); + + alsa_element_close (ALSA_ELEMENT (control)); + stream->priv->controls = g_list_delete_link (stream->priv->controls, item); + + /* Change the default control if we have just removed it */ + if (control == mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream))) { + AlsaStreamControl *first = NULL; + + if (stream->priv->controls != NULL) + first = ALSA_STREAM_CONTROL (stream->priv->controls->data); + + alsa_stream_set_default_control (stream, first); + } + + g_signal_emit_by_name (G_OBJECT (stream), + "control-removed", + mate_mixer_stream_control_get_name (control)); + + g_object_unref (control); removed = TRUE; - if (g_hash_table_remove (stream->priv->switches, name) == TRUE) + } + + item = g_list_find_custom (stream->priv->switches, name, compare_switch_name); + if (item != NULL) { + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (item->data); + + alsa_element_close (ALSA_ELEMENT (swtch)); + + stream->priv->switches = g_list_delete_link (stream->priv->switches, item); + g_signal_emit_by_name (G_OBJECT (stream), + "switch-removed", + mate_mixer_switch_get_name (swtch)); + + g_object_unref (swtch); removed = TRUE; + } return removed; } -static MateMixerStreamControl * -alsa_stream_get_control (MateMixerStream *mms, const gchar *name) +void +alsa_stream_remove_all (AlsaStream *stream) { - g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); + GList *list; - return g_hash_table_lookup (ALSA_STREAM (mms)->priv->controls, name); -} + g_return_if_fail (ALSA_IS_STREAM (stream)); -static MateMixerStreamControl * -alsa_stream_get_default_control (MateMixerStream *mms) -{ - g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); + /* Remove all stream controls */ + list = stream->priv->controls; + while (list != NULL) { + MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (list->data); + GList *next = list->next; + + alsa_element_close (ALSA_ELEMENT (control)); + + stream->priv->controls = g_list_delete_link (stream->priv->controls, list); + g_signal_emit_by_name (G_OBJECT (stream), + "control-removed", + mate_mixer_stream_control_get_name (control)); + + g_object_unref (control); + list = next; + } + + /* Unset the default stream control */ + alsa_stream_set_default_control (stream, NULL); + + /* Remove all stream switches */ + list = stream->priv->switches; + while (list != NULL) { + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (list->data); + GList *next = list->next; + + alsa_element_close (ALSA_ELEMENT (swtch)); - return ALSA_STREAM (mms)->priv->control; + stream->priv->switches = g_list_delete_link (stream->priv->switches, list); + g_signal_emit_by_name (G_OBJECT (stream), + "switch-removed", + mate_mixer_switch_get_name (swtch)); + + g_object_unref (swtch); + list = next; + } } -static MateMixerSwitch * -alsa_stream_get_switch (MateMixerStream *mms, const gchar *name) +static const GList * +alsa_stream_list_controls (MateMixerStream *mms) { g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); - return g_hash_table_lookup (ALSA_STREAM (mms)->priv->switches, name); + return ALSA_STREAM (mms)->priv->controls; } -static GList * -alsa_stream_list_controls (MateMixerStream *mms) +static const 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->controls); - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + return ALSA_STREAM (mms)->priv->switches; } -static GList * -alsa_stream_list_switches (MateMixerStream *mms) +static gint +compare_control_name (gconstpointer a, gconstpointer b) { - GList *list; + MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (a); + const gchar *name = (const gchar *) b; - g_return_val_if_fail (ALSA_IS_STREAM (mms), NULL); + return strcmp (mate_mixer_stream_control_get_name (control), name); +} - /* 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); +static gint +compare_switch_name (gconstpointer a, gconstpointer b) +{ + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (a); + const gchar *name = (const gchar *) b; - return list; + return strcmp (mate_mixer_switch_get_name (swtch), name); } diff --git a/backends/alsa/alsa-stream.h b/backends/alsa/alsa-stream.h index f26a643..5aa3095 100644 --- a/backends/alsa/alsa-stream.h +++ b/backends/alsa/alsa-stream.h @@ -22,9 +22,9 @@ #include <glib-object.h> #include <libmatemixer/matemixer.h> -#include "alsa-element.h" #include "alsa-stream-control.h" #include "alsa-switch.h" +#include "alsa-toggle.h" G_BEGIN_DECLS @@ -58,30 +58,35 @@ struct _AlsaStreamClass MateMixerStreamClass parent_class; }; -GType alsa_stream_get_type (void) G_GNUC_CONST; +GType alsa_stream_get_type (void) G_GNUC_CONST; -AlsaStream *alsa_stream_new (const gchar *name, - MateMixerDevice *device, - MateMixerStreamFlags flags); +AlsaStream * alsa_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerDirection direction); -void alsa_stream_add_control (AlsaStream *stream, - AlsaStreamControl *control); +void alsa_stream_add_control (AlsaStream *stream, + AlsaStreamControl *control); +void alsa_stream_add_switch (AlsaStream *stream, + AlsaSwitch *swtch); +void alsa_stream_add_toggle (AlsaStream *stream, + AlsaToggle *toggle); -void alsa_stream_add_switch (AlsaStream *stream, - AlsaSwitch *swtch); +gboolean alsa_stream_has_controls (AlsaStream *stream); +gboolean alsa_stream_has_switches (AlsaStream *stream); +gboolean alsa_stream_has_controls_or_switches (AlsaStream *stream); +gboolean alsa_stream_has_default_control (AlsaStream *stream); -gboolean alsa_stream_is_empty (AlsaStream *stream); +AlsaStreamControl *alsa_stream_get_default_control (AlsaStream *stream); +void alsa_stream_set_default_control (AlsaStream *stream, + AlsaStreamControl *control); -void alsa_stream_set_default_control (AlsaStream *stream, - AlsaStreamControl *control); +void alsa_stream_load_elements (AlsaStream *stream, + const gchar *name); -void alsa_stream_load_elements (AlsaStream *stream, - const gchar *name); +gboolean alsa_stream_remove_elements (AlsaStream *stream, + const gchar *name); -gboolean alsa_stream_remove_elements (AlsaStream *stream, - const gchar *name); - -void alsa_stream_remove_all (AlsaStream *stream); +void alsa_stream_remove_all (AlsaStream *stream); G_END_DECLS diff --git a/backends/alsa/alsa-switch-option.c b/backends/alsa/alsa-switch-option.c index 2173113..1800df2 100644 --- a/backends/alsa/alsa-switch-option.c +++ b/backends/alsa/alsa-switch-option.c @@ -18,9 +18,7 @@ #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" @@ -59,6 +57,7 @@ alsa_switch_option_new (const gchar *name, option = g_object_new (ALSA_TYPE_SWITCH_OPTION, "name", name, "label", label, + "icon", icon, NULL); option->priv->id = id; diff --git a/backends/alsa/alsa-switch-option.h b/backends/alsa/alsa-switch-option.h index c2dda87..98d4f57 100644 --- a/backends/alsa/alsa-switch-option.h +++ b/backends/alsa/alsa-switch-option.h @@ -24,17 +24,17 @@ G_BEGIN_DECLS -#define ALSA_TYPE_SWITCH_OPTION \ +#define ALSA_TYPE_SWITCH_OPTION \ (alsa_switch_option_get_type ()) -#define ALSA_SWITCH_OPTION(o) \ +#define ALSA_SWITCH_OPTION(o) \ (G_TYPE_CHECK_INSTANCE_CAST ((o), ALSA_TYPE_SWITCH_OPTION, AlsaSwitchOption)) -#define ALSA_IS_SWITCH_OPTION(o) \ +#define ALSA_IS_SWITCH_OPTION(o) \ (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALSA_TYPE_SWITCH_OPTION)) -#define ALSA_SWITCH_OPTION_CLASS(k) \ +#define ALSA_SWITCH_OPTION_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST ((k), ALSA_TYPE_SWITCH_OPTION, AlsaSwitchOptionClass)) -#define ALSA_IS_SWITCH_OPTION_CLASS(k) \ +#define ALSA_IS_SWITCH_OPTION_CLASS(k) \ (G_TYPE_CHECK_CLASS_TYPE ((k), ALSA_TYPE_SWITCH_OPTION)) -#define ALSA_SWITCH_OPTION_GET_CLASS(o) \ +#define ALSA_SWITCH_OPTION_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), ALSA_TYPE_SWITCH_OPTION, AlsaSwitchOptionClass)) typedef struct _AlsaSwitchOption AlsaSwitchOption; diff --git a/backends/alsa/alsa-switch.c b/backends/alsa/alsa-switch.c index 15151ae..6a0f1f4 100644 --- a/backends/alsa/alsa-switch.c +++ b/backends/alsa/alsa-switch.c @@ -37,6 +37,7 @@ static void alsa_element_interface_init (AlsaElementInterface *iface); static void alsa_switch_class_init (AlsaSwitchClass *klass); static void alsa_switch_init (AlsaSwitch *swtch); +static void alsa_switch_dispose (GObject *object); G_DEFINE_TYPE_WITH_CODE (AlsaSwitch, alsa_switch, MATE_MIXER_TYPE_SWITCH, @@ -46,7 +47,7 @@ G_DEFINE_TYPE_WITH_CODE (AlsaSwitch, alsa_switch, static gboolean alsa_switch_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso); -static GList * alsa_switch_list_options (MateMixerSwitch *mms); +static const 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, @@ -64,8 +65,12 @@ alsa_element_interface_init (AlsaElementInterface *iface) static void alsa_switch_class_init (AlsaSwitchClass *klass) { + GObjectClass *object_class; MateMixerSwitchClass *switch_class; + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = alsa_switch_dispose; + 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; @@ -74,6 +79,21 @@ alsa_switch_class_init (AlsaSwitchClass *klass) } static void +alsa_switch_dispose (GObject *object) +{ + AlsaSwitch *swtch; + + swtch = ALSA_SWITCH (object); + + if (swtch->priv->options != NULL) { + g_list_free_full (swtch->priv->options, g_object_unref); + swtch->priv->options = NULL; + } + + G_OBJECT_CLASS (alsa_switch_parent_class)->dispose (object); +} + +static void alsa_switch_init (AlsaSwitch *swtch) { swtch->priv = G_TYPE_INSTANCE_GET_PRIVATE (swtch, @@ -82,13 +102,17 @@ alsa_switch_init (AlsaSwitch *swtch) } AlsaSwitch * -alsa_switch_new (const gchar *name, const gchar *label, GList *options) +alsa_switch_new (const gchar *name, + const gchar *label, + MateMixerSwitchRole role, + GList *options) { AlsaSwitch *swtch; swtch = g_object_new (ALSA_TYPE_SWITCH, "name", name, "label", label, + "role", role, NULL); /* Takes ownership of options */ @@ -109,6 +133,9 @@ alsa_switch_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso swtch = ALSA_SWITCH (mms); + if G_UNLIKELY (swtch->priv->element == NULL) + return FALSE; + /* 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) { @@ -136,12 +163,12 @@ alsa_switch_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso return set_item; } -static GList * -alsa_switch_list_options (MateMixerSwitch *swtch) +static const GList * +alsa_switch_list_options (MateMixerSwitch *mms) { - g_return_val_if_fail (ALSA_IS_SWITCH (swtch), NULL); + g_return_val_if_fail (ALSA_IS_SWITCH (mms), NULL); - return ALSA_SWITCH (swtch)->priv->options; + return ALSA_SWITCH (mms)->priv->options; } static snd_mixer_elem_t * @@ -156,7 +183,6 @@ 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; } @@ -170,8 +196,13 @@ alsa_switch_load (AlsaElement *element) gint ret; snd_mixer_selem_channel_id_t c; + g_return_val_if_fail (ALSA_IS_SWITCH (element), FALSE); + swtch = ALSA_SWITCH (element); + if G_UNLIKELY (swtch->priv->element == NULL) + return FALSE; + /* When reading the first time we try all the channels, otherwise only the * ones which returned success before */ if (swtch->priv->channel_mask == 0) { @@ -220,7 +251,7 @@ alsa_switch_load (AlsaElement *element) } g_warning ("Unknown active option of switch %s: %d", - snd_mixer_selem_get_name (swtch->priv->element), + mate_mixer_switch_get_name (MATE_MIXER_SWITCH (swtch)), item); return FALSE; diff --git a/backends/alsa/alsa-switch.h b/backends/alsa/alsa-switch.h index fdcfb87..b7f5931 100644 --- a/backends/alsa/alsa-switch.h +++ b/backends/alsa/alsa-switch.h @@ -56,9 +56,10 @@ struct _AlsaSwitchClass GType alsa_switch_get_type (void) G_GNUC_CONST; -AlsaSwitch *alsa_switch_new (const gchar *name, - const gchar *label, - GList *options); +AlsaSwitch *alsa_switch_new (const gchar *name, + const gchar *label, + MateMixerSwitchRole role, + GList *options); G_END_DECLS diff --git a/backends/alsa/alsa-toggle.c b/backends/alsa/alsa-toggle.c index efa3460..a7958c9 100644 --- a/backends/alsa/alsa-toggle.c +++ b/backends/alsa/alsa-toggle.c @@ -42,13 +42,13 @@ 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 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 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) @@ -78,17 +78,20 @@ alsa_toggle_init (AlsaToggle *toggle) } AlsaToggle * -alsa_toggle_new (const gchar *name, - const gchar *label, - AlsaToggleType type, - AlsaSwitchOption *on, - AlsaSwitchOption *off) +alsa_toggle_new (const gchar *name, + const gchar *label, + MateMixerSwitchRole role, + AlsaToggleType type, + AlsaSwitchOption *on, + AlsaSwitchOption *off) { AlsaToggle *toggle; toggle = g_object_new (ALSA_TYPE_TOGGLE, "name", name, "label", label, + "flags", MATE_MIXER_SWITCH_TOGGLE, + "role", role, "state-option-on", on, "state-option-off", off, NULL); @@ -109,7 +112,12 @@ alsa_toggle_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso toggle = ALSA_TOGGLE (mms); - /* For toggles the 0/1 value is stored as the switch option id */ + if G_UNLIKELY (toggle->priv->element == NULL) + return FALSE; + + /* For toggles the 0/1 value is stored as the switch option id, there is not really + * a need to validate that the option belong to the switch, just make sure it + * contains the value 0 or 1 */ value = alsa_switch_option_get_id (ALSA_SWITCH_OPTION (mmso)); if G_UNLIKELY (value != 0 && value != 1) { g_warn_if_reached (); @@ -143,7 +151,6 @@ 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; } @@ -158,6 +165,9 @@ alsa_toggle_load (AlsaElement *element) toggle = ALSA_TOGGLE (element); + if G_UNLIKELY (toggle->priv->element == NULL) + return FALSE; + /* When reading the first time we try all the channels, otherwise only the * ones which returned success before */ if (toggle->priv->channel_mask == 0) { @@ -207,7 +217,6 @@ alsa_toggle_load (AlsaElement *element) 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; } diff --git a/backends/alsa/alsa-toggle.h b/backends/alsa/alsa-toggle.h index d9c083b..1e1993c 100644 --- a/backends/alsa/alsa-toggle.h +++ b/backends/alsa/alsa-toggle.h @@ -63,11 +63,12 @@ struct _AlsaToggleClass 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); +AlsaToggle *alsa_toggle_new (const gchar *name, + const gchar *label, + MateMixerSwitchRole role, + AlsaToggleType type, + AlsaSwitchOption *on, + AlsaSwitchOption *off); G_END_DECLS |