/* * mixer_controls.c - handles mixer controls and mapping from selems * Copyright (c) 1998,1999 Tim Janik * Jaroslav Kysela * Copyright (c) 2009 Clemens Ladisch * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "aconfig.h" #include #include #include #include CURSESINC #include #include "utils.h" #include "mem.h" #include "mixer_display.h" #include "mixer_widget.h" #include "mixer_controls.h" struct control *controls; unsigned int controls_count; static const snd_mixer_selem_channel_id_t supported_channels[] = { SND_MIXER_SCHN_FRONT_LEFT, SND_MIXER_SCHN_FRONT_RIGHT, SND_MIXER_SCHN_REAR_LEFT, SND_MIXER_SCHN_REAR_RIGHT, SND_MIXER_SCHN_FRONT_CENTER, SND_MIXER_SCHN_WOOFER, SND_MIXER_SCHN_SIDE_LEFT, SND_MIXER_SCHN_SIDE_RIGHT, }; #define LAST_SUPPORTED_CHANNEL SND_MIXER_SCHN_SIDE_RIGHT static const snd_mixer_selem_channel_id_t control_channels[][2] = { { SND_MIXER_SCHN_FRONT_LEFT, SND_MIXER_SCHN_FRONT_RIGHT }, { SND_MIXER_SCHN_REAR_LEFT, SND_MIXER_SCHN_REAR_RIGHT }, { SND_MIXER_SCHN_FRONT_CENTER, SND_MIXER_SCHN_UNKNOWN }, { SND_MIXER_SCHN_WOOFER, SND_MIXER_SCHN_UNKNOWN }, { SND_MIXER_SCHN_SIDE_LEFT, SND_MIXER_SCHN_SIDE_RIGHT }, }; bool are_there_any_controls(void) { snd_mixer_elem_t *elem; unsigned int i; for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) { if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE) continue; if (snd_mixer_selem_is_enumerated(elem)) return TRUE; if (snd_mixer_selem_has_playback_volume_joined(elem) || snd_mixer_selem_has_capture_volume_joined(elem) || snd_mixer_selem_has_playback_switch_joined(elem) || snd_mixer_selem_has_capture_switch_joined(elem)) return TRUE; for (i = 0; i < ARRAY_SIZE(supported_channels); ++i) if (snd_mixer_selem_has_playback_channel(elem, supported_channels[i]) || snd_mixer_selem_has_capture_channel(elem, supported_channels[i])) return TRUE; } return FALSE; } static bool has_more_than_front_capture_channels(snd_mixer_elem_t *elem) { unsigned int i; for (i = 2; i < ARRAY_SIZE(supported_channels); ++i) if (snd_mixer_selem_has_capture_channel(elem, supported_channels[i])) return TRUE; return FALSE; } static bool has_any_control_channel(snd_mixer_elem_t *elem, const snd_mixer_selem_channel_id_t channels[2], int (*has_channel)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t)) { return has_channel(elem, channels[0]) || (channels[1] != SND_MIXER_SCHN_UNKNOWN && has_channel(elem, channels[1])); } static bool has_merged_cswitch(snd_mixer_elem_t *elem) { bool pvol, psw; unsigned int i; pvol = snd_mixer_selem_has_playback_volume(elem); psw = snd_mixer_selem_has_playback_switch(elem); if ((pvol || psw) && snd_mixer_selem_has_capture_switch(elem) && !snd_mixer_selem_has_capture_volume(elem)) { if (snd_mixer_selem_has_capture_switch_joined(elem)) return TRUE; else if (((pvol && snd_mixer_selem_has_playback_volume_joined(elem)) || (psw && snd_mixer_selem_has_playback_switch_joined(elem))) && has_more_than_front_capture_channels(elem)) return FALSE; for (i = 0; i < ARRAY_SIZE(control_channels); ++i) { if (has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_capture_channel) && !has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_playback_channel)) return FALSE; } return TRUE; } return FALSE; } static unsigned int get_playback_controls_count(snd_mixer_elem_t *elem) { unsigned int count = 0; unsigned int i; int has_vol, has_sw; has_vol = snd_mixer_selem_has_playback_volume(elem); has_sw = snd_mixer_selem_has_playback_switch(elem); if (!has_vol && !has_sw) return 0; if ((!has_vol || snd_mixer_selem_has_playback_volume_joined(elem)) && (!has_sw || snd_mixer_selem_has_playback_switch_joined(elem))) return 1; for (i = 0; i < ARRAY_SIZE(control_channels); ++i) { if (snd_mixer_selem_has_playback_channel(elem, control_channels[i][0]) || (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN && snd_mixer_selem_has_playback_channel(elem, control_channels[i][1]))) ++count; } return count; } static unsigned int get_capture_controls_count(snd_mixer_elem_t *elem) { unsigned int count = 0; unsigned int i; int has_vol, has_sw; has_vol = snd_mixer_selem_has_capture_volume(elem); has_sw = snd_mixer_selem_has_capture_switch(elem); if ((!has_vol && !has_sw) || (view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem))) return 0; if ((!has_vol || snd_mixer_selem_has_capture_volume_joined(elem)) && (!has_sw || snd_mixer_selem_has_capture_switch_joined(elem))) return 1; for (i = 0; i < ARRAY_SIZE(control_channels); ++i) { if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0]) || (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN && snd_mixer_selem_has_capture_channel(elem, control_channels[i][1]))) ++count; } return count; } static unsigned int get_controls_count_for_elem(snd_mixer_elem_t *elem) { unsigned int p, c; if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE) return 0; if (snd_mixer_selem_is_enumerated(elem)) { switch (view_mode) { case VIEW_MODE_PLAYBACK: return snd_mixer_selem_is_enum_capture(elem) ? 0 : 1; case VIEW_MODE_CAPTURE: return snd_mixer_selem_is_enum_capture(elem) ? 1 : 0; case VIEW_MODE_ALL: default: return 1; } } switch (view_mode) { case VIEW_MODE_PLAYBACK: return get_playback_controls_count(elem); case VIEW_MODE_CAPTURE: return get_capture_controls_count(elem); case VIEW_MODE_ALL: default: p = get_playback_controls_count(elem); c = get_capture_controls_count(elem); return has_merged_cswitch(elem) ? p : p + c; } } static void create_name(struct control *control) { unsigned int index; char *s; index = snd_mixer_selem_get_index(control->elem); if (index > 0) control->name = casprintf("%s %u", snd_mixer_selem_get_name(control->elem), index); else control->name = cstrdup(snd_mixer_selem_get_name(control->elem)); while ((s = strstr(control->name, "IEC958")) != NULL) memcpy(s, "S/PDIF", 6); } static unsigned int create_controls_for_elem(snd_mixer_elem_t *elem, struct control *control) { unsigned int count = 0; unsigned int i; unsigned int enum_index; struct control *front_control = NULL; bool has_pvol, has_psw; bool has_cvol, has_csw; bool has_channel[LAST_SUPPORTED_CHANNEL + 1]; bool merged_cswitch; bool has_ch0, has_ch1; if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE) return 0; if (snd_mixer_selem_is_enumerated(elem)) { if ((view_mode == VIEW_MODE_PLAYBACK && snd_mixer_selem_is_enum_capture(elem)) || (view_mode == VIEW_MODE_CAPTURE && !snd_mixer_selem_is_enum_capture(elem))) return 0; control->elem = elem; control->flags = TYPE_ENUM; control->enum_channel_bits = 0; for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i) if (snd_mixer_selem_get_enum_item(control->elem, (snd_mixer_selem_channel_id_t)i, &enum_index) >= 0) control->enum_channel_bits |= 1 << i; if (snd_mixer_selem_is_active(control->elem)) control->flags |= IS_ACTIVE; create_name(control); return 1; } has_pvol = snd_mixer_selem_has_playback_volume(elem); has_psw = snd_mixer_selem_has_playback_switch(elem); has_cvol = snd_mixer_selem_has_capture_volume(elem); has_csw = snd_mixer_selem_has_capture_switch(elem); merged_cswitch = view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem); if (view_mode != VIEW_MODE_CAPTURE && (has_pvol || has_psw)) { if ((!has_pvol || snd_mixer_selem_has_playback_volume_joined(elem)) && (!has_psw || snd_mixer_selem_has_playback_switch_joined(elem))) { control->elem = elem; if (has_pvol) { control->flags |= TYPE_PVOLUME | HAS_VOLUME_0; control->volume_channels[0] = 0; } if (has_psw) { control->flags |= TYPE_PSWITCH | HAS_PSWITCH_0; control->pswitch_channels[0] = 0; } if (merged_cswitch) { control->flags |= TYPE_CSWITCH; if (snd_mixer_selem_has_capture_switch_joined(elem)) { control->flags |= HAS_CSWITCH_0; control->cswitch_channels[0] = 0; } else { if (snd_mixer_selem_has_capture_channel(elem, control_channels[0][0])) { control->flags |= HAS_CSWITCH_0; control->cswitch_channels[0] = control_channels[0][0]; } if (control_channels[0][1] != SND_MIXER_SCHN_UNKNOWN && snd_mixer_selem_has_capture_channel(elem, control_channels[0][1])) { control->flags |= HAS_CSWITCH_1; control->cswitch_channels[1] = control_channels[0][1]; } } if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) { control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1; control->cswitch_channels[0] = control->cswitch_channels[1]; } } if (snd_mixer_selem_is_active(control->elem)) control->flags |= IS_ACTIVE; create_name(control); ++control; ++count; } else { for (i = 0; i < ARRAY_SIZE(supported_channels); ++i) has_channel[supported_channels[i]] = snd_mixer_selem_has_playback_channel(elem, supported_channels[i]); for (i = 0; i < ARRAY_SIZE(control_channels); ++i) { has_ch0 = has_channel[control_channels[i][0]]; has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN && has_channel[control_channels[i][1]]; if (!has_ch0 && !has_ch1) continue; control->elem = elem; if (has_pvol) { control->flags |= TYPE_PVOLUME; if (snd_mixer_selem_has_playback_volume_joined(elem)) { control->flags |= HAS_VOLUME_0; control->volume_channels[0] = 0; } else { if (has_ch0) { control->flags |= HAS_VOLUME_0; control->volume_channels[0] = control_channels[i][0]; } if (has_ch1) { control->flags |= HAS_VOLUME_1; control->volume_channels[1] = control_channels[i][1]; } } } if (has_psw) { control->flags |= TYPE_PSWITCH; if (snd_mixer_selem_has_playback_switch_joined(elem)) { control->flags |= HAS_PSWITCH_0; control->pswitch_channels[0] = 0; } else { if (has_ch0) { control->flags |= HAS_PSWITCH_0; control->pswitch_channels[0] = control_channels[i][0]; } if (has_ch1) { control->flags |= HAS_PSWITCH_1; control->pswitch_channels[1] = control_channels[i][1]; } } } if (merged_cswitch) { control->flags |= TYPE_CSWITCH; if (snd_mixer_selem_has_capture_switch_joined(elem)) { control->flags |= HAS_CSWITCH_0; control->cswitch_channels[0] = 0; } else { if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0])) { control->flags |= HAS_CSWITCH_0; control->cswitch_channels[0] = control_channels[i][0]; } if (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN && snd_mixer_selem_has_capture_channel(elem, control_channels[i][1])) { control->flags |= HAS_CSWITCH_1; control->cswitch_channels[1] = control_channels[i][1]; } } } if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) { control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1; control->volume_channels[0] = control->volume_channels[1]; } if ((control->flags & (HAS_PSWITCH_0 | HAS_PSWITCH_1)) == HAS_PSWITCH_1) { control->flags ^= HAS_PSWITCH_0 | HAS_PSWITCH_1; control->pswitch_channels[0] = control->pswitch_channels[1]; } if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) { control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1; control->cswitch_channels[0] = control->cswitch_channels[1]; } if (snd_mixer_selem_is_active(control->elem)) control->flags |= IS_ACTIVE; create_name(control); if (i == 0) front_control = control; else { front_control->flags |= IS_MULTICH | 0; control->flags |= IS_MULTICH | i; } ++control; ++count; } } } if (view_mode != VIEW_MODE_PLAYBACK && (has_cvol || has_csw) && !merged_cswitch) { if ((!has_cvol || snd_mixer_selem_has_capture_volume_joined(elem)) && (!has_csw || snd_mixer_selem_has_capture_switch_joined(elem))) { control->elem = elem; if (has_cvol) { control->flags |= TYPE_CVOLUME | HAS_VOLUME_0; control->volume_channels[0] = 0; } if (has_csw) { control->flags |= TYPE_CSWITCH | HAS_CSWITCH_0; control->cswitch_channels[0] = 0; } if (snd_mixer_selem_is_active(control->elem)) control->flags |= IS_ACTIVE; create_name(control); ++control; ++count; } else { for (i = 0; i < ARRAY_SIZE(supported_channels); ++i) has_channel[supported_channels[i]] = snd_mixer_selem_has_capture_channel(elem, supported_channels[i]); for (i = 0; i < ARRAY_SIZE(control_channels); ++i) { has_ch0 = has_channel[control_channels[i][0]]; has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN && has_channel[control_channels[i][1]]; if (!has_ch0 && !has_ch1) continue; control->elem = elem; if (has_cvol) { control->flags |= TYPE_CVOLUME; if (snd_mixer_selem_has_capture_volume_joined(elem)) { control->flags |= HAS_VOLUME_0; control->volume_channels[0] = 0; } else { if (has_ch0) { control->flags |= HAS_VOLUME_0; control->volume_channels[0] = control_channels[i][0]; } if (has_ch1) { control->flags |= HAS_VOLUME_1; control->volume_channels[1] = control_channels[i][1]; } } } if (has_csw) { control->flags |= TYPE_CSWITCH; if (snd_mixer_selem_has_capture_switch_joined(elem)) { control->flags |= HAS_CSWITCH_0; control->cswitch_channels[0] = 0; } else { if (has_ch0) { control->flags |= HAS_CSWITCH_0; control->cswitch_channels[0] = control_channels[i][0]; } if (has_ch1) { control->flags |= HAS_CSWITCH_1; control->cswitch_channels[1] = control_channels[i][1]; } } } if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) { control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1; control->volume_channels[0] = control->volume_channels[1]; } if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) { control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1; control->cswitch_channels[0] = control->cswitch_channels[1]; } if (snd_mixer_selem_is_active(control->elem)) control->flags |= IS_ACTIVE; create_name(control); if (i == 0) front_control = control; else { front_control->flags |= IS_MULTICH | 0; control->flags |= IS_MULTICH | i; } ++control; ++count; } } } return count; } static void search_for_focus_control(void) { snd_mixer_elem_t *elem; unsigned int i; elem = snd_mixer_find_selem(mixer, current_selem_id); if (elem) for (i = 0; i < controls_count; ++i) if (controls[i].elem == elem) { focus_control_index = i; for (;;) { ++i; if (i >= controls_count || controls[i].elem != elem) return; if (controls[i].flags == current_control_flags) { focus_control_index = i; return; } } } focus_control_index = 0; } void free_controls(void) { unsigned int i; for (i = 0; i < controls_count; ++i) free(controls[i].name); free(controls); controls = NULL; controls_count = 0; } void create_controls(void) { snd_mixer_elem_t *elem; struct control *control; free_controls(); for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) controls_count += get_controls_count_for_elem(elem); if (controls_count > 0) { controls = ccalloc(controls_count, sizeof *controls); control = controls; for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) control += create_controls_for_elem(elem, control); assert(control == controls + controls_count); } compute_controls_layout(); display_view_mode(); search_for_focus_control(); refocus_control(); }