diff options
Diffstat (limited to 'backends/alsa/alsa-device.c')
-rw-r--r-- | backends/alsa/alsa-device.c | 690 |
1 files changed, 480 insertions, 210 deletions
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); } |