|  | /** | 
|  | * \file mixer/simple_none.c | 
|  | * \brief Mixer Simple Element Class Interface | 
|  | * \author Jaroslav Kysela <perex@perex.cz> | 
|  | * \author Abramo Bagnara <abramo@alsa-project.org> | 
|  | * \date 2001-2004 | 
|  | * | 
|  | * Mixer simple element class interface. | 
|  | */ | 
|  | /* | 
|  | *  Mixer Interface - simple controls | 
|  | *  Copyright (c) 2000,2004 by Jaroslav Kysela <perex@perex.cz> | 
|  | *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org> | 
|  | * | 
|  | * | 
|  | *   This library is free software; you can redistribute it and/or modify | 
|  | *   it under the terms of the GNU Lesser General Public License as | 
|  | *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. | 
|  | * | 
|  | *   You should have received a copy of the GNU Lesser General Public | 
|  | *   License along with this library; if not, write to the Free Software | 
|  | *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <unistd.h> | 
|  | #include <string.h> | 
|  | #include <fcntl.h> | 
|  | #include <sys/ioctl.h> | 
|  | #include <assert.h> | 
|  | #include <math.h> | 
|  | #include <limits.h> | 
|  | #include "local.h" | 
|  | #include "config.h" | 
|  | #include "mixer_simple.h" | 
|  |  | 
|  | #ifndef DOC_HIDDEN | 
|  |  | 
|  | #define MIXER_COMPARE_WEIGHT_SIMPLE_BASE        0 | 
|  | #define MIXER_COMPARE_WEIGHT_NEXT_BASE          10000000 | 
|  | #define MIXER_COMPARE_WEIGHT_NOT_FOUND          1000000000 | 
|  |  | 
|  | typedef enum _selem_ctl_type { | 
|  | CTL_SINGLE, | 
|  | CTL_GLOBAL_ENUM, | 
|  | CTL_GLOBAL_SWITCH, | 
|  | CTL_GLOBAL_VOLUME, | 
|  | CTL_GLOBAL_ROUTE, | 
|  | CTL_PLAYBACK_ENUM, | 
|  | CTL_PLAYBACK_SWITCH, | 
|  | CTL_PLAYBACK_VOLUME, | 
|  | CTL_PLAYBACK_ROUTE, | 
|  | CTL_CAPTURE_ENUM, | 
|  | CTL_CAPTURE_SWITCH, | 
|  | CTL_CAPTURE_VOLUME, | 
|  | CTL_CAPTURE_ROUTE, | 
|  | CTL_CAPTURE_SOURCE, | 
|  | CTL_LAST = CTL_CAPTURE_SOURCE, | 
|  | } selem_ctl_type_t; | 
|  |  | 
|  | typedef struct _selem_ctl { | 
|  | snd_hctl_elem_t *elem; | 
|  | snd_ctl_elem_type_t type; | 
|  | unsigned int inactive: 1; | 
|  | unsigned int values; | 
|  | long min, max; | 
|  | } selem_ctl_t; | 
|  |  | 
|  | typedef struct _selem_none { | 
|  | sm_selem_t selem; | 
|  | selem_ctl_t ctls[CTL_LAST + 1]; | 
|  | unsigned int capture_item; | 
|  | struct selem_str { | 
|  | unsigned int range: 1;	/* Forced range */ | 
|  | unsigned int db_initialized: 1; | 
|  | unsigned int db_init_error: 1; | 
|  | long min, max; | 
|  | unsigned int channels; | 
|  | long vol[32]; | 
|  | unsigned int sw; | 
|  | unsigned int *db_info; | 
|  | } str[2]; | 
|  | } selem_none_t; | 
|  |  | 
|  | static const struct mixer_name_table { | 
|  | const char *longname; | 
|  | const char *shortname; | 
|  | } name_table[] = { | 
|  | {"Tone Control - Switch", "Tone"}, | 
|  | {"Tone Control - Bass", "Bass"}, | 
|  | {"Tone Control - Treble", "Treble"}, | 
|  | {"Synth Tone Control - Switch", "Synth Tone"}, | 
|  | {"Synth Tone Control - Bass", "Synth Bass"}, | 
|  | {"Synth Tone Control - Treble", "Synth Treble"}, | 
|  | {0, 0}, | 
|  | }; | 
|  |  | 
|  | #endif /* !DOC_HIDDEN */ | 
|  |  | 
|  | static const char *get_short_name(const char *lname) | 
|  | { | 
|  | const struct mixer_name_table *p; | 
|  | for (p = name_table; p->longname; p++) { | 
|  | if (!strcmp(lname, p->longname)) | 
|  | return p->shortname; | 
|  | } | 
|  | return lname; | 
|  | } | 
|  |  | 
|  | static int compare_mixer_priority_lookup(const char **name, const char * const *names, int coef) | 
|  | { | 
|  | int res; | 
|  |  | 
|  | for (res = 0; *names; names++, res += coef) { | 
|  | if (!strncmp(*name, *names, strlen(*names))) { | 
|  | *name += strlen(*names); | 
|  | if (**name == ' ') | 
|  | (*name)++; | 
|  | return res+1; | 
|  | } | 
|  | } | 
|  | return MIXER_COMPARE_WEIGHT_NOT_FOUND; | 
|  | } | 
|  |  | 
|  | static int get_compare_weight(const char *name, unsigned int idx) | 
|  | { | 
|  | static const char *const names[] = { | 
|  | "Master", | 
|  | "Headphone", | 
|  | "Speaker", | 
|  | "Tone", | 
|  | "Bass", | 
|  | "Treble", | 
|  | "3D Control", | 
|  | "PCM", | 
|  | "Front", | 
|  | "Surround", | 
|  | "Center", | 
|  | "LFE", | 
|  | "Side", | 
|  | "Synth", | 
|  | "FM", | 
|  | "Wave", | 
|  | "Music", | 
|  | "DSP", | 
|  | "Line", | 
|  | "CD", | 
|  | "Mic", | 
|  | "Video", | 
|  | "Zoom Video", | 
|  | "Phone", | 
|  | "I2S", | 
|  | "IEC958", | 
|  | "PC Speaker", | 
|  | "Beep", | 
|  | "Aux", | 
|  | "Mono", | 
|  | "Playback", | 
|  | "Capture", | 
|  | "Mix", | 
|  | NULL | 
|  | }; | 
|  | static const char *const names1[] = { | 
|  | "-", | 
|  | NULL, | 
|  | }; | 
|  | static const char *const names2[] = { | 
|  | "Mono", | 
|  | "Digital", | 
|  | "Switch", | 
|  | "Depth", | 
|  | "Wide", | 
|  | "Space", | 
|  | "Level", | 
|  | "Center", | 
|  | "Output", | 
|  | "Boost", | 
|  | "Tone", | 
|  | "Bass", | 
|  | "Treble", | 
|  | NULL, | 
|  | }; | 
|  | const char *name1; | 
|  | int res, res1; | 
|  |  | 
|  | if ((res = compare_mixer_priority_lookup((const char **)&name, names, 1000)) == MIXER_COMPARE_WEIGHT_NOT_FOUND) | 
|  | return MIXER_COMPARE_WEIGHT_NOT_FOUND; | 
|  | if (*name == '\0') | 
|  | goto __res; | 
|  | for (name1 = name; *name1 != '\0'; name1++); | 
|  | for (name1--; name1 != name && *name1 != ' '; name1--); | 
|  | while (name1 != name && *name1 == ' ') | 
|  | name1--; | 
|  | if (name1 != name) { | 
|  | for (; name1 != name && *name1 != ' '; name1--); | 
|  | name = name1; | 
|  | if ((res1 = compare_mixer_priority_lookup((const char **)&name, names1, 200)) == MIXER_COMPARE_WEIGHT_NOT_FOUND) | 
|  | return res; | 
|  | res += res1; | 
|  | } else { | 
|  | name = name1; | 
|  | } | 
|  | if ((res1 = compare_mixer_priority_lookup((const char **)&name, names2, 20)) == MIXER_COMPARE_WEIGHT_NOT_FOUND) | 
|  | return res; | 
|  | __res: | 
|  | return MIXER_COMPARE_WEIGHT_SIMPLE_BASE + res + idx; | 
|  | } | 
|  |  | 
|  | static long to_user(selem_none_t *s, int dir, selem_ctl_t *c, long value) | 
|  | { | 
|  | int64_t n; | 
|  | if (c->max == c->min) | 
|  | return s->str[dir].min; | 
|  | n = (int64_t) (value - c->min) * (s->str[dir].max - s->str[dir].min); | 
|  | return s->str[dir].min + (n + (c->max - c->min) / 2) / (c->max - c->min); | 
|  | } | 
|  |  | 
|  | static long from_user(selem_none_t *s, int dir, selem_ctl_t *c, long value) | 
|  | { | 
|  | int64_t n; | 
|  | if (s->str[dir].max == s->str[dir].min) | 
|  | return c->min; | 
|  | n = (int64_t) (value - s->str[dir].min) * (c->max - c->min); | 
|  | return c->min + (n + (s->str[dir].max - s->str[dir].min) / 2) / (s->str[dir].max - s->str[dir].min); | 
|  | } | 
|  |  | 
|  | static int elem_read_volume(selem_none_t *s, int dir, selem_ctl_type_t type) | 
|  | { | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | unsigned int idx; | 
|  | int err; | 
|  | selem_ctl_t *c = &s->ctls[type]; | 
|  | if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | for (idx = 0; idx < s->str[dir].channels; idx++) { | 
|  | unsigned int idx1 = idx; | 
|  | if (idx >= c->values) | 
|  | idx1 = 0; | 
|  | s->str[dir].vol[idx] = | 
|  | to_user(s, dir, c, | 
|  | snd_ctl_elem_value_get_integer(&ctl, idx1)); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int elem_read_switch(selem_none_t *s, int dir, selem_ctl_type_t type) | 
|  | { | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | unsigned int idx; | 
|  | int err; | 
|  | selem_ctl_t *c = &s->ctls[type]; | 
|  | if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | for (idx = 0; idx < s->str[dir].channels; idx++) { | 
|  | unsigned int idx1 = idx; | 
|  | if (idx >= c->values) | 
|  | idx1 = 0; | 
|  | if (!snd_ctl_elem_value_get_integer(&ctl, idx1)) | 
|  | s->str[dir].sw &= ~(1 << idx); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int elem_read_route(selem_none_t *s, int dir, selem_ctl_type_t type) | 
|  | { | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | unsigned int idx; | 
|  | int err; | 
|  | selem_ctl_t *c = &s->ctls[type]; | 
|  | if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | for (idx = 0; idx < s->str[dir].channels; idx++) { | 
|  | unsigned int idx1 = idx; | 
|  | if (idx >= c->values) | 
|  | idx1 = 0; | 
|  | if (!snd_ctl_elem_value_get_integer(&ctl, | 
|  | idx1 * c->values + idx1)) | 
|  | s->str[dir].sw &= ~(1 << idx); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int elem_read_enum(selem_none_t *s) | 
|  | { | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | unsigned int idx; | 
|  | int err; | 
|  | int type; | 
|  | selem_ctl_t *c; | 
|  | type = CTL_GLOBAL_ENUM; | 
|  | if ((s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM)) == | 
|  | (SM_CAP_CENUM | SM_CAP_PENUM)) | 
|  | type = CTL_GLOBAL_ENUM; | 
|  | else if (s->selem.caps & SM_CAP_PENUM) | 
|  | type = CTL_PLAYBACK_ENUM; | 
|  | else if (s->selem.caps & SM_CAP_CENUM) | 
|  | type = CTL_CAPTURE_ENUM; | 
|  | c = &s->ctls[type]; | 
|  | if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | for (idx = 0; idx < s->str[0].channels; idx++) { | 
|  | unsigned int idx1 = idx; | 
|  | if (idx >= c->values) | 
|  | idx1 = 0; | 
|  | s->str[0].vol[idx] = | 
|  | snd_ctl_elem_value_get_enumerated(&ctl, idx1); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int selem_read(snd_mixer_elem_t *elem) | 
|  | { | 
|  | selem_none_t *s; | 
|  | unsigned int idx; | 
|  | int err = 0; | 
|  | long pvol[32], cvol[32]; | 
|  | unsigned int psw, csw; | 
|  |  | 
|  | assert(snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE); | 
|  | s = snd_mixer_elem_get_private(elem); | 
|  |  | 
|  | memcpy(pvol, s->str[SM_PLAY].vol, sizeof(pvol)); | 
|  | memset(&s->str[SM_PLAY].vol, 0, sizeof(s->str[SM_PLAY].vol)); | 
|  | psw = s->str[SM_PLAY].sw; | 
|  | s->str[SM_PLAY].sw = ~0U; | 
|  | memcpy(cvol, s->str[SM_CAPT].vol, sizeof(cvol)); | 
|  | memset(&s->str[SM_CAPT].vol, 0, sizeof(s->str[SM_CAPT].vol)); | 
|  | csw = s->str[SM_CAPT].sw; | 
|  | s->str[SM_CAPT].sw = ~0U; | 
|  |  | 
|  | if (s->ctls[CTL_GLOBAL_ENUM].elem) { | 
|  | err = elem_read_enum(s); | 
|  | if (err < 0) | 
|  | return err; | 
|  | goto __skip_cswitch; | 
|  | } | 
|  |  | 
|  | if (s->ctls[CTL_CAPTURE_ENUM].elem) { | 
|  | err = elem_read_enum(s); | 
|  | if (err < 0) | 
|  | return err; | 
|  | goto __skip_cswitch; | 
|  | } | 
|  |  | 
|  | if (s->ctls[CTL_PLAYBACK_ENUM].elem) { | 
|  | err = elem_read_enum(s); | 
|  | if (err < 0) | 
|  | return err; | 
|  | goto __skip_cswitch; | 
|  | } | 
|  |  | 
|  |  | 
|  | if (s->ctls[CTL_PLAYBACK_VOLUME].elem) | 
|  | err = elem_read_volume(s, SM_PLAY, CTL_PLAYBACK_VOLUME); | 
|  | else if (s->ctls[CTL_GLOBAL_VOLUME].elem) | 
|  | err = elem_read_volume(s, SM_PLAY, CTL_GLOBAL_VOLUME); | 
|  | else if (s->ctls[CTL_SINGLE].elem && | 
|  | s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_INTEGER) | 
|  | err = elem_read_volume(s, SM_PLAY, CTL_SINGLE); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | if ((s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH)) == 0) { | 
|  | s->str[SM_PLAY].sw = 0; | 
|  | goto __skip_pswitch; | 
|  | } | 
|  | if (s->ctls[CTL_PLAYBACK_SWITCH].elem) { | 
|  | err = elem_read_switch(s, SM_PLAY, CTL_PLAYBACK_SWITCH); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_GLOBAL_SWITCH].elem) { | 
|  | err = elem_read_switch(s, SM_PLAY, CTL_GLOBAL_SWITCH); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_SINGLE].elem && | 
|  | s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_BOOLEAN) { | 
|  | err = elem_read_switch(s, SM_PLAY, CTL_SINGLE); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_PLAYBACK_ROUTE].elem) { | 
|  | err = elem_read_route(s, SM_PLAY, CTL_PLAYBACK_ROUTE); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_GLOBAL_ROUTE].elem) { | 
|  | err = elem_read_route(s, SM_PLAY, CTL_GLOBAL_ROUTE); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | __skip_pswitch: | 
|  |  | 
|  | if (s->ctls[CTL_CAPTURE_VOLUME].elem) | 
|  | err = elem_read_volume(s, SM_CAPT, CTL_CAPTURE_VOLUME); | 
|  | else if (s->ctls[CTL_GLOBAL_VOLUME].elem) | 
|  | err = elem_read_volume(s, SM_CAPT, CTL_GLOBAL_VOLUME); | 
|  | else if (s->ctls[CTL_SINGLE].elem && | 
|  | s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_INTEGER) | 
|  | err = elem_read_volume(s, SM_CAPT, CTL_SINGLE); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | if ((s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_CSWITCH)) == 0) { | 
|  | s->str[SM_CAPT].sw = 0; | 
|  | goto __skip_cswitch; | 
|  | } | 
|  | if (s->ctls[CTL_CAPTURE_SWITCH].elem) { | 
|  | err = elem_read_switch(s, SM_CAPT, CTL_CAPTURE_SWITCH); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_GLOBAL_SWITCH].elem) { | 
|  | err = elem_read_switch(s, SM_CAPT, CTL_GLOBAL_SWITCH); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_SINGLE].elem && | 
|  | s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_BOOLEAN) { | 
|  | err = elem_read_switch(s, SM_CAPT, CTL_SINGLE); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_CAPTURE_ROUTE].elem) { | 
|  | err = elem_read_route(s, SM_CAPT, CTL_CAPTURE_ROUTE); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_GLOBAL_ROUTE].elem) { | 
|  | err = elem_read_route(s, SM_CAPT, CTL_GLOBAL_ROUTE); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_CAPTURE_SOURCE].elem) { | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | selem_ctl_t *c = &s->ctls[CTL_CAPTURE_SOURCE]; | 
|  | err = snd_hctl_elem_read(c->elem, &ctl); | 
|  | if (err < 0) | 
|  | return err; | 
|  | for (idx = 0; idx < s->str[SM_CAPT].channels; idx++) { | 
|  | unsigned int idx1 = idx; | 
|  | if (idx >= c->values) | 
|  | idx1 = 0; | 
|  | if (snd_ctl_elem_value_get_enumerated(&ctl, idx1) != | 
|  | s->capture_item) | 
|  | s->str[SM_CAPT].sw &= ~(1 << idx); | 
|  | } | 
|  | } | 
|  | __skip_cswitch: | 
|  |  | 
|  | if (memcmp(pvol, s->str[SM_PLAY].vol, sizeof(pvol)) || | 
|  | psw != s->str[SM_PLAY].sw || | 
|  | memcmp(cvol, s->str[SM_CAPT].vol, sizeof(cvol)) || | 
|  | csw != s->str[SM_CAPT].sw) | 
|  | return 1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int elem_write_volume(selem_none_t *s, int dir, selem_ctl_type_t type) | 
|  | { | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | unsigned int idx; | 
|  | int err; | 
|  | selem_ctl_t *c = &s->ctls[type]; | 
|  | if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | for (idx = 0; idx < c->values; idx++) | 
|  | snd_ctl_elem_value_set_integer(&ctl, idx, | 
|  | from_user(s, dir, c, s->str[dir].vol[idx])); | 
|  | if ((err = snd_hctl_elem_write(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int elem_write_switch(selem_none_t *s, int dir, selem_ctl_type_t type) | 
|  | { | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | unsigned int idx; | 
|  | int err; | 
|  | selem_ctl_t *c = &s->ctls[type]; | 
|  | if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | for (idx = 0; idx < c->values; idx++) | 
|  | snd_ctl_elem_value_set_integer(&ctl, idx, | 
|  | !!(s->str[dir].sw & (1 << idx))); | 
|  | if ((err = snd_hctl_elem_write(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int elem_write_switch_constant(selem_none_t *s, selem_ctl_type_t type, int val) | 
|  | { | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | unsigned int idx; | 
|  | int err; | 
|  | selem_ctl_t *c = &s->ctls[type]; | 
|  | if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | for (idx = 0; idx < c->values; idx++) | 
|  | snd_ctl_elem_value_set_integer(&ctl, idx, !!val); | 
|  | if ((err = snd_hctl_elem_write(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int elem_write_route(selem_none_t *s, int dir, selem_ctl_type_t type) | 
|  | { | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | unsigned int idx; | 
|  | int err; | 
|  | selem_ctl_t *c = &s->ctls[type]; | 
|  | if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | for (idx = 0; idx < c->values * c->values; idx++) | 
|  | snd_ctl_elem_value_set_integer(&ctl, idx, 0); | 
|  | for (idx = 0; idx < c->values; idx++) | 
|  | snd_ctl_elem_value_set_integer(&ctl, idx * c->values + idx, | 
|  | !!(s->str[dir].sw & (1 << idx))); | 
|  | if ((err = snd_hctl_elem_write(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int elem_write_enum(selem_none_t *s) | 
|  | { | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | unsigned int idx; | 
|  | int err; | 
|  | int type; | 
|  | selem_ctl_t *c; | 
|  | type = CTL_GLOBAL_ENUM; | 
|  | if ((s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM)) == | 
|  | (SM_CAP_CENUM | SM_CAP_PENUM)) | 
|  | type = CTL_GLOBAL_ENUM; | 
|  | else if (s->selem.caps & SM_CAP_PENUM) | 
|  | type = CTL_PLAYBACK_ENUM; | 
|  | else if (s->selem.caps & SM_CAP_CENUM) | 
|  | type = CTL_CAPTURE_ENUM; | 
|  | c = &s->ctls[type]; | 
|  | if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | for (idx = 0; idx < c->values; idx++) | 
|  | snd_ctl_elem_value_set_enumerated(&ctl, idx, | 
|  | (unsigned int)s->str[0].vol[idx]); | 
|  | if ((err = snd_hctl_elem_write(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int selem_write_main(snd_mixer_elem_t *elem) | 
|  | { | 
|  | selem_none_t *s; | 
|  | unsigned int idx; | 
|  | int err; | 
|  |  | 
|  | assert(snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE); | 
|  | s = snd_mixer_elem_get_private(elem); | 
|  |  | 
|  | if (s->ctls[CTL_GLOBAL_ENUM].elem) | 
|  | return elem_write_enum(s); | 
|  |  | 
|  | if (s->ctls[CTL_PLAYBACK_ENUM].elem) | 
|  | return elem_write_enum(s); | 
|  |  | 
|  | if (s->ctls[CTL_CAPTURE_ENUM].elem) | 
|  | return elem_write_enum(s); | 
|  |  | 
|  | if (s->ctls[CTL_SINGLE].elem) { | 
|  | if (s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_INTEGER) | 
|  | err = elem_write_volume(s, SM_PLAY, CTL_SINGLE); | 
|  | else | 
|  | err = elem_write_switch(s, SM_PLAY, CTL_SINGLE); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_GLOBAL_VOLUME].elem) { | 
|  | err = elem_write_volume(s, SM_PLAY, CTL_GLOBAL_VOLUME); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_GLOBAL_SWITCH].elem) { | 
|  | if (s->ctls[CTL_PLAYBACK_SWITCH].elem && | 
|  | s->ctls[CTL_CAPTURE_SWITCH].elem) | 
|  | err = elem_write_switch_constant(s, CTL_GLOBAL_SWITCH, | 
|  | 1); | 
|  | else | 
|  | err = elem_write_switch(s, SM_PLAY, CTL_GLOBAL_SWITCH); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_PLAYBACK_VOLUME].elem) { | 
|  | err = elem_write_volume(s, SM_PLAY, CTL_PLAYBACK_VOLUME); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_PLAYBACK_SWITCH].elem) { | 
|  | err = elem_write_switch(s, SM_PLAY, CTL_PLAYBACK_SWITCH); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_PLAYBACK_ROUTE].elem) { | 
|  | err = elem_write_route(s, SM_PLAY, CTL_PLAYBACK_ROUTE); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_CAPTURE_VOLUME].elem) { | 
|  | err = elem_write_volume(s, SM_CAPT, CTL_CAPTURE_VOLUME); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_CAPTURE_SWITCH].elem) { | 
|  | err = elem_write_switch(s, SM_CAPT, CTL_CAPTURE_SWITCH); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_CAPTURE_ROUTE].elem) { | 
|  | err = elem_write_route(s, SM_CAPT, CTL_CAPTURE_ROUTE); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (s->ctls[CTL_CAPTURE_SOURCE].elem) { | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | selem_ctl_t *c = &s->ctls[CTL_CAPTURE_SOURCE]; | 
|  | if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | for (idx = 0; idx < c->values; idx++) { | 
|  | if (s->str[SM_CAPT].sw & (1 << idx)) | 
|  | snd_ctl_elem_value_set_enumerated(&ctl, | 
|  | idx, s->capture_item); | 
|  | } | 
|  | if ((err = snd_hctl_elem_write(c->elem, &ctl)) < 0) | 
|  | return err; | 
|  | /* update the element, don't remove */ | 
|  | err = selem_read(elem); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int selem_write(snd_mixer_elem_t *elem) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | err = selem_write_main(elem); | 
|  | if (err < 0) | 
|  | selem_read(elem); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static void selem_free(snd_mixer_elem_t *elem) | 
|  | { | 
|  | selem_none_t *simple = snd_mixer_elem_get_private(elem); | 
|  | assert(snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE); | 
|  | if (simple->selem.id) | 
|  | snd_mixer_selem_id_free(simple->selem.id); | 
|  | /* free db range information */ | 
|  | free(simple->str[0].db_info); | 
|  | free(simple->str[1].db_info); | 
|  | free(simple); | 
|  | } | 
|  |  | 
|  | static int simple_update(snd_mixer_elem_t *melem) | 
|  | { | 
|  | selem_none_t *simple; | 
|  | unsigned int caps, pchannels, cchannels; | 
|  | long pmin, pmax, cmin, cmax; | 
|  | selem_ctl_t *ctl; | 
|  |  | 
|  | caps = 0; | 
|  | pchannels = 0; | 
|  | pmin = LONG_MAX; | 
|  | pmax = LONG_MIN; | 
|  | cchannels = 0; | 
|  | cmin = LONG_MAX; | 
|  | cmax = LONG_MIN; | 
|  | assert(snd_mixer_elem_get_type(melem) == SND_MIXER_ELEM_SIMPLE); | 
|  | simple = snd_mixer_elem_get_private(melem); | 
|  | ctl = &simple->ctls[CTL_SINGLE]; | 
|  | if (ctl->elem) { | 
|  | pchannels = cchannels = ctl->values; | 
|  | if (ctl->type == SND_CTL_ELEM_TYPE_INTEGER) { | 
|  | caps |= SM_CAP_GVOLUME; | 
|  | pmin = cmin = ctl->min; | 
|  | pmax = cmax = ctl->max; | 
|  | } else | 
|  | caps |= SM_CAP_GSWITCH; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_GLOBAL_SWITCH]; | 
|  | if (ctl->elem) { | 
|  | if (pchannels < ctl->values) | 
|  | pchannels = ctl->values; | 
|  | if (cchannels < ctl->values) | 
|  | cchannels = ctl->values; | 
|  | caps |= SM_CAP_GSWITCH; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_GLOBAL_ROUTE]; | 
|  | if (ctl->elem) { | 
|  | if (pchannels < ctl->values) | 
|  | pchannels = ctl->values; | 
|  | if (cchannels < ctl->values) | 
|  | cchannels = ctl->values; | 
|  | caps |= SM_CAP_GSWITCH; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_GLOBAL_VOLUME]; | 
|  | if (ctl->elem) { | 
|  | if (pchannels < ctl->values) | 
|  | pchannels = ctl->values; | 
|  | if (pmin > ctl->min) | 
|  | pmin = ctl->min; | 
|  | if (pmax < ctl->max) | 
|  | pmax = ctl->max; | 
|  | if (cchannels < ctl->values) | 
|  | cchannels = ctl->values; | 
|  | if (cmin > ctl->min) | 
|  | cmin = ctl->min; | 
|  | if (cmax < ctl->max) | 
|  | cmax = ctl->max; | 
|  | caps |= SM_CAP_GVOLUME; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_PLAYBACK_SWITCH]; | 
|  | if (ctl->elem) { | 
|  | if (pchannels < ctl->values) | 
|  | pchannels = ctl->values; | 
|  | caps |= SM_CAP_PSWITCH; | 
|  | caps &= ~SM_CAP_GSWITCH; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_PLAYBACK_ROUTE]; | 
|  | if (ctl->elem) { | 
|  | if (pchannels < ctl->values) | 
|  | pchannels = ctl->values; | 
|  | caps |= SM_CAP_PSWITCH; | 
|  | caps &= ~SM_CAP_GSWITCH; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_CAPTURE_SWITCH]; | 
|  | if (ctl->elem) { | 
|  | if (cchannels < ctl->values) | 
|  | cchannels = ctl->values; | 
|  | caps |= SM_CAP_CSWITCH; | 
|  | caps &= ~SM_CAP_GSWITCH; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_CAPTURE_ROUTE]; | 
|  | if (ctl->elem) { | 
|  | if (cchannels < ctl->values) | 
|  | cchannels = ctl->values; | 
|  | caps |= SM_CAP_CSWITCH; | 
|  | caps &= ~SM_CAP_GSWITCH; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_PLAYBACK_VOLUME]; | 
|  | if (ctl->elem) { | 
|  | if (pchannels < ctl->values) | 
|  | pchannels = ctl->values; | 
|  | if (pmin > ctl->min) | 
|  | pmin = ctl->min; | 
|  | if (pmax < ctl->max) | 
|  | pmax = ctl->max; | 
|  | caps |= SM_CAP_PVOLUME; | 
|  | caps &= ~SM_CAP_GVOLUME; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_CAPTURE_VOLUME]; | 
|  | if (ctl->elem) { | 
|  | if (cchannels < ctl->values) | 
|  | cchannels = ctl->values; | 
|  | if (cmin > ctl->min) | 
|  | cmin = ctl->min; | 
|  | if (cmax < ctl->max) | 
|  | cmax = ctl->max; | 
|  | caps |= SM_CAP_CVOLUME; | 
|  | caps &= ~SM_CAP_GVOLUME; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_CAPTURE_SOURCE]; | 
|  | if (ctl->elem) { | 
|  | if (cchannels < ctl->values) | 
|  | cchannels = ctl->values; | 
|  | caps |= SM_CAP_CSWITCH | SM_CAP_CSWITCH_EXCL; | 
|  | caps &= ~SM_CAP_GSWITCH; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_GLOBAL_ENUM]; | 
|  | if (ctl->elem) { | 
|  | if (pchannels < ctl->values) | 
|  | pchannels = ctl->values; | 
|  | caps |= SM_CAP_PENUM | SM_CAP_CENUM; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_PLAYBACK_ENUM]; | 
|  | if (ctl->elem) { | 
|  | if (pchannels < ctl->values) | 
|  | pchannels = ctl->values; | 
|  | caps |= SM_CAP_PENUM; | 
|  | } | 
|  | ctl = &simple->ctls[CTL_CAPTURE_ENUM]; | 
|  | if (ctl->elem) { | 
|  | if (pchannels < ctl->values) | 
|  | pchannels = ctl->values; | 
|  | caps |= SM_CAP_CENUM; | 
|  | } | 
|  | if (pchannels > 32) | 
|  | pchannels = 32; | 
|  | if (cchannels > 32) | 
|  | cchannels = 32; | 
|  | if (caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH)) | 
|  | caps |= SM_CAP_PSWITCH_JOIN; | 
|  | if (caps & (SM_CAP_GVOLUME|SM_CAP_PVOLUME)) | 
|  | caps |= SM_CAP_PVOLUME_JOIN; | 
|  | if (caps & (SM_CAP_GSWITCH|SM_CAP_CSWITCH)) | 
|  | caps |= SM_CAP_CSWITCH_JOIN; | 
|  | if (caps & (SM_CAP_GVOLUME|SM_CAP_CVOLUME)) | 
|  | caps |= SM_CAP_CVOLUME_JOIN; | 
|  | if (pchannels > 1 || cchannels > 1) { | 
|  | if (simple->ctls[CTL_SINGLE].elem && | 
|  | simple->ctls[CTL_SINGLE].values > 1) { | 
|  | if (caps & SM_CAP_GSWITCH) | 
|  | caps &= ~(SM_CAP_PSWITCH_JOIN|SM_CAP_CSWITCH_JOIN); | 
|  | else | 
|  | caps &= ~(SM_CAP_PVOLUME_JOIN|SM_CAP_CVOLUME_JOIN); | 
|  | } | 
|  | if (simple->ctls[CTL_GLOBAL_ROUTE].elem || | 
|  | (simple->ctls[CTL_GLOBAL_SWITCH].elem && | 
|  | simple->ctls[CTL_GLOBAL_SWITCH].values > 1)) { | 
|  | caps &= ~(SM_CAP_PSWITCH_JOIN|SM_CAP_CSWITCH_JOIN); | 
|  | } | 
|  | if (simple->ctls[CTL_GLOBAL_VOLUME].elem && | 
|  | simple->ctls[CTL_GLOBAL_VOLUME].values > 1) { | 
|  | caps &= ~(SM_CAP_PVOLUME_JOIN|SM_CAP_CVOLUME_JOIN); | 
|  | } | 
|  | } | 
|  | if (pchannels > 1) { | 
|  | if (simple->ctls[CTL_PLAYBACK_ROUTE].elem || | 
|  | (simple->ctls[CTL_PLAYBACK_SWITCH].elem && | 
|  | simple->ctls[CTL_PLAYBACK_SWITCH].values > 1)) { | 
|  | caps &= ~SM_CAP_PSWITCH_JOIN; | 
|  | } | 
|  | if (simple->ctls[CTL_PLAYBACK_VOLUME].elem && | 
|  | simple->ctls[CTL_PLAYBACK_VOLUME].values > 1) { | 
|  | caps &= ~SM_CAP_PVOLUME_JOIN; | 
|  | } | 
|  | } | 
|  | if (cchannels > 1) { | 
|  | if (simple->ctls[CTL_CAPTURE_ROUTE].elem || | 
|  | (simple->ctls[CTL_CAPTURE_SWITCH].elem && | 
|  | simple->ctls[CTL_CAPTURE_SWITCH].values > 1) || | 
|  | (simple->ctls[CTL_CAPTURE_SOURCE].elem && | 
|  | simple->ctls[CTL_CAPTURE_SOURCE].values > 1)) { | 
|  | caps &= ~SM_CAP_CSWITCH_JOIN; | 
|  | } | 
|  | if (simple->ctls[CTL_CAPTURE_VOLUME].elem && | 
|  | simple->ctls[CTL_CAPTURE_VOLUME].values > 1) { | 
|  | caps &= ~SM_CAP_CVOLUME_JOIN; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* exceptions */ | 
|  | if ((caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH|SM_CAP_CSWITCH)) && | 
|  | (caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH|SM_CAP_CSWITCH)) == (caps & SM_CAP_GSWITCH)) { | 
|  | caps &= ~(SM_CAP_GSWITCH|SM_CAP_CSWITCH_JOIN|SM_CAP_CSWITCH_EXCL); | 
|  | caps |= SM_CAP_PSWITCH; | 
|  | } | 
|  |  | 
|  | if ((caps & SM_CAP_GSWITCH) && | 
|  | (caps & (SM_CAP_PSWITCH|SM_CAP_CSWITCH)) == 0) | 
|  | caps |= SM_CAP_PSWITCH|SM_CAP_CSWITCH; | 
|  |  | 
|  | if ((caps & SM_CAP_GVOLUME) && | 
|  | (caps & (SM_CAP_PVOLUME|SM_CAP_CVOLUME)) == 0) | 
|  | caps |= SM_CAP_PVOLUME|SM_CAP_CVOLUME; | 
|  |  | 
|  | simple->selem.caps = caps; | 
|  | simple->str[SM_PLAY].channels = pchannels; | 
|  | if (!simple->str[SM_PLAY].range) { | 
|  | simple->str[SM_PLAY].min = pmin != LONG_MAX ? pmin : 0; | 
|  | simple->str[SM_PLAY].max = pmax != LONG_MIN ? pmax : 0; | 
|  | } | 
|  | simple->str[SM_CAPT].channels = cchannels; | 
|  | if (!simple->str[SM_CAPT].range) { | 
|  | simple->str[SM_CAPT].min = cmin != LONG_MAX ? cmin : 0; | 
|  | simple->str[SM_CAPT].max = cmax != LONG_MIN ? cmax : 0; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #ifndef DOC_HIDDEN | 
|  | static const struct suf { | 
|  | const char *suffix; | 
|  | selem_ctl_type_t type; | 
|  | } suffixes[] = { | 
|  | {" Playback Enum", CTL_PLAYBACK_ENUM}, | 
|  | {" Playback Switch", CTL_PLAYBACK_SWITCH}, | 
|  | {" Playback Route", CTL_PLAYBACK_ROUTE}, | 
|  | {" Playback Volume", CTL_PLAYBACK_VOLUME}, | 
|  | {" Capture Enum", CTL_CAPTURE_ENUM}, | 
|  | {" Capture Switch", CTL_CAPTURE_SWITCH}, | 
|  | {" Capture Route", CTL_CAPTURE_ROUTE}, | 
|  | {" Capture Volume", CTL_CAPTURE_VOLUME}, | 
|  | {" Enum", CTL_GLOBAL_ENUM}, | 
|  | {" Switch", CTL_GLOBAL_SWITCH}, | 
|  | {" Route", CTL_GLOBAL_ROUTE}, | 
|  | {" Volume", CTL_GLOBAL_VOLUME}, | 
|  | {NULL, 0} | 
|  | }; | 
|  | #endif | 
|  |  | 
|  | /* Return base length or 0 on failure */ | 
|  | static int base_len(const char *name, selem_ctl_type_t *type) | 
|  | { | 
|  | const struct suf *p; | 
|  | size_t nlen = strlen(name); | 
|  | p = suffixes; | 
|  | while (p->suffix) { | 
|  | size_t slen = strlen(p->suffix); | 
|  | size_t l; | 
|  | if (nlen > slen) { | 
|  | l = nlen - slen; | 
|  | if (strncmp(name + l, p->suffix, slen) == 0 && | 
|  | (l < 1 || name[l-1] != '-')) {	/* 3D Control - Switch */ | 
|  | *type = p->type; | 
|  | return l; | 
|  | } | 
|  | } | 
|  | p++; | 
|  | } | 
|  |  | 
|  | /* Special case - handle "Input Source" as a capture route. | 
|  | * Note that it's *NO* capture source.  A capture source is split over | 
|  | * sub-elements, and multiple capture-sources will result in an error. | 
|  | * That's why some drivers use "Input Source" as a workaround. | 
|  | * Hence, this is a workaround for a workaround to get the things | 
|  | * straight back again.  Sigh. | 
|  | */ | 
|  | if (!strcmp(name, "Input Source")) { | 
|  | *type = CTL_CAPTURE_ROUTE; | 
|  | return strlen(name); | 
|  | } | 
|  | if (strstr(name, "3D Control")) { | 
|  | if (strstr(name, "Depth")) { | 
|  | *type = CTL_PLAYBACK_VOLUME; | 
|  | return strlen(name); | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Simple Mixer Operations | 
|  | */ | 
|  |  | 
|  | static int _snd_mixer_selem_set_volume(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, long value) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | if (s->selem.caps & SM_CAP_GVOLUME) | 
|  | dir = SM_PLAY; | 
|  | if ((unsigned int) channel >= s->str[dir].channels) | 
|  | return 0; | 
|  | if (value < s->str[dir].min || value > s->str[dir].max) | 
|  | return 0; | 
|  | if (s->selem.caps & | 
|  | (dir == SM_PLAY ? SM_CAP_PVOLUME_JOIN : SM_CAP_CVOLUME_JOIN)) | 
|  | channel = 0; | 
|  | if (value != s->str[dir].vol[channel]) { | 
|  | s->str[dir].vol[channel] = value; | 
|  | return 1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int _snd_mixer_selem_set_switch(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, int value) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | if ((unsigned int) channel >= s->str[dir].channels) | 
|  | return 0; | 
|  | if (s->selem.caps & | 
|  | (dir == SM_PLAY ? SM_CAP_PSWITCH_JOIN : SM_CAP_CSWITCH_JOIN)) | 
|  | channel = 0; | 
|  | if (value) { | 
|  | if (!(s->str[dir].sw & (1 << channel))) { | 
|  | s->str[dir].sw |= 1 << channel; | 
|  | return 1; | 
|  | } | 
|  | } else { | 
|  | if (s->str[dir].sw & (1 << channel)) { | 
|  | s->str[dir].sw &= ~(1 << channel); | 
|  | return 1; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int is_ops(snd_mixer_elem_t *elem, int dir, int cmd, int val) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  |  | 
|  | switch (cmd) { | 
|  |  | 
|  | case SM_OPS_IS_ACTIVE: { | 
|  | selem_ctl_type_t ctl; | 
|  | for (ctl = CTL_SINGLE; ctl <= CTL_LAST; ctl++) | 
|  | if (s->ctls[ctl].elem != NULL && s->ctls[ctl].inactive) | 
|  | return 0; | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | case SM_OPS_IS_MONO: | 
|  | return s->str[dir].channels == 1; | 
|  |  | 
|  | case SM_OPS_IS_CHANNEL: | 
|  | return (unsigned int) val < s->str[dir].channels; | 
|  |  | 
|  | case SM_OPS_IS_ENUMERATED: | 
|  | if (val == 1) { | 
|  | if (dir == SM_PLAY && (s->selem.caps & SM_CAP_PENUM) && !(s->selem.caps & SM_CAP_CENUM) ) | 
|  | return 1; | 
|  | if (dir == SM_CAPT && (s->selem.caps & SM_CAP_CENUM) && !(s->selem.caps & SM_CAP_PENUM) ) | 
|  | return 1; | 
|  | return 0; | 
|  | } | 
|  | if (s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM) ) | 
|  | return 1; | 
|  | return 0; | 
|  |  | 
|  | case SM_OPS_IS_ENUMCNT: | 
|  | /* Both */ | 
|  | if ( (s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM)) == (SM_CAP_CENUM | SM_CAP_PENUM) ) { | 
|  | if (! s->ctls[CTL_GLOBAL_ENUM].elem) | 
|  | return -EINVAL; | 
|  | return s->ctls[CTL_GLOBAL_ENUM].max; | 
|  | /* Only Playback */ | 
|  | } else if (s->selem.caps & SM_CAP_PENUM ) { | 
|  | if (! s->ctls[CTL_PLAYBACK_ENUM].elem) | 
|  | return -EINVAL; | 
|  | return s->ctls[CTL_PLAYBACK_ENUM].max; | 
|  | /* Only Capture */ | 
|  | } else if (s->selem.caps & SM_CAP_CENUM ) { | 
|  | if (! s->ctls[CTL_CAPTURE_ENUM].elem) | 
|  | return -EINVAL; | 
|  | return s->ctls[CTL_CAPTURE_ENUM].max; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static int get_range_ops(snd_mixer_elem_t *elem, int dir, | 
|  | long *min, long *max) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | *min = s->str[dir].min; | 
|  | *max = s->str[dir].max; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int set_range_ops(snd_mixer_elem_t *elem, int dir, | 
|  | long min, long max) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | int err; | 
|  |  | 
|  | s->str[dir].range = 1; | 
|  | s->str[dir].min = min; | 
|  | s->str[dir].max = max; | 
|  | if ((err = selem_read(elem)) < 0) | 
|  | return err; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int get_volume_ops(snd_mixer_elem_t *elem, int dir, | 
|  | snd_mixer_selem_channel_id_t channel, long *value) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | if (s->selem.caps & SM_CAP_GVOLUME) | 
|  | dir = SM_PLAY; | 
|  | if ((unsigned int) channel >= s->str[dir].channels) | 
|  | return -EINVAL; | 
|  | *value = s->str[dir].vol[channel]; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int init_db_range(snd_hctl_elem_t *ctl, struct selem_str *rec); | 
|  |  | 
|  | static int convert_to_dB(snd_hctl_elem_t *ctl, struct selem_str *rec, | 
|  | long volume, long *db_gain) | 
|  | { | 
|  | if (init_db_range(ctl, rec) < 0) | 
|  | return -EINVAL; | 
|  | return snd_tlv_convert_to_dB(rec->db_info, rec->min, rec->max, | 
|  | volume, db_gain); | 
|  | } | 
|  |  | 
|  | /* initialize dB range information, reading TLV via hcontrol | 
|  | */ | 
|  | static int init_db_range(snd_hctl_elem_t *ctl, struct selem_str *rec) | 
|  | { | 
|  | snd_ctl_elem_info_t info = {0}; | 
|  | unsigned int *tlv = NULL; | 
|  | const unsigned int tlv_size = 4096; | 
|  | unsigned int *dbrec; | 
|  | int db_size; | 
|  |  | 
|  | if (rec->db_init_error) | 
|  | return -EINVAL; | 
|  | if (rec->db_initialized) | 
|  | return 0; | 
|  |  | 
|  | if (snd_hctl_elem_info(ctl, &info) < 0) | 
|  | goto error; | 
|  | if (!snd_ctl_elem_info_is_tlv_readable(&info)) | 
|  | goto error; | 
|  | tlv = malloc(tlv_size); | 
|  | if (!tlv) | 
|  | return -ENOMEM; | 
|  | if (snd_hctl_elem_tlv_read(ctl, tlv, tlv_size) < 0) | 
|  | goto error; | 
|  | db_size = snd_tlv_parse_dB_info(tlv, tlv_size, &dbrec); | 
|  | if (db_size < 0) | 
|  | goto error; | 
|  | rec->db_info = malloc(db_size); | 
|  | if (!rec->db_info) | 
|  | goto error; | 
|  | memcpy(rec->db_info, dbrec, db_size); | 
|  | free(tlv); | 
|  | rec->db_initialized = 1; | 
|  | return 0; | 
|  |  | 
|  | error: | 
|  | free(tlv); | 
|  | rec->db_init_error = 1; | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* get selem_ctl for TLV access */ | 
|  | static selem_ctl_t *get_selem_ctl(selem_none_t *s, int dir) | 
|  | { | 
|  | selem_ctl_t *c; | 
|  | if (dir == SM_PLAY) | 
|  | c = &s->ctls[CTL_PLAYBACK_VOLUME]; | 
|  | else if (dir == SM_CAPT) | 
|  | c = &s->ctls[CTL_CAPTURE_VOLUME]; | 
|  | else | 
|  | return NULL; | 
|  | if (! c->elem) { | 
|  | c = &s->ctls[CTL_GLOBAL_VOLUME]; | 
|  | if (! c->elem) | 
|  | return NULL; | 
|  | } | 
|  | if (c->type != SND_CTL_ELEM_TYPE_INTEGER) | 
|  | return NULL; | 
|  | return c; | 
|  | } | 
|  |  | 
|  | static int get_dB_range(snd_hctl_elem_t *ctl, struct selem_str *rec, | 
|  | long *min, long *max) | 
|  | { | 
|  | if (init_db_range(ctl, rec) < 0) | 
|  | return -EINVAL; | 
|  |  | 
|  | return snd_tlv_get_dB_range(rec->db_info, rec->min, rec->max, min, max); | 
|  | } | 
|  |  | 
|  | static int get_dB_range_ops(snd_mixer_elem_t *elem, int dir, | 
|  | long *min, long *max) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | selem_ctl_t *c; | 
|  |  | 
|  | if (s->selem.caps & SM_CAP_GVOLUME) | 
|  | dir = SM_PLAY; | 
|  | c = get_selem_ctl(s, dir); | 
|  | if (! c) | 
|  | return -EINVAL; | 
|  | return get_dB_range(c->elem, &s->str[dir], min, max); | 
|  | } | 
|  |  | 
|  | static int convert_from_dB(snd_hctl_elem_t *ctl, struct selem_str *rec, | 
|  | long db_gain, long *value, int xdir) | 
|  | { | 
|  | if (init_db_range(ctl, rec) < 0) | 
|  | return -EINVAL; | 
|  |  | 
|  | return snd_tlv_convert_from_dB(rec->db_info, rec->min, rec->max, | 
|  | db_gain, value, xdir); | 
|  | } | 
|  |  | 
|  | static int ask_vol_dB_ops(snd_mixer_elem_t *elem, | 
|  | int dir, | 
|  | long value, | 
|  | long *dBvalue) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | selem_ctl_t *c; | 
|  |  | 
|  | c = get_selem_ctl(s, dir); | 
|  | if (! c) | 
|  | return -EINVAL; | 
|  | int res = convert_to_dB(c->elem, &s->str[dir], value, dBvalue); | 
|  | return res; | 
|  | } | 
|  |  | 
|  | static int get_dB_ops(snd_mixer_elem_t *elem, | 
|  | int dir, | 
|  | snd_mixer_selem_channel_id_t channel, | 
|  | long *value) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | selem_ctl_t *c; | 
|  | int err; | 
|  | long volume, db_gain; | 
|  |  | 
|  | if (s->selem.caps & SM_CAP_GVOLUME) | 
|  | dir = SM_PLAY; | 
|  | c = get_selem_ctl(s, dir); | 
|  | if (! c) | 
|  | return -EINVAL; | 
|  | if ((err = get_volume_ops(elem, dir, channel, &volume)) < 0) | 
|  | goto _err; | 
|  | if ((err = convert_to_dB(c->elem, &s->str[dir], volume, &db_gain)) < 0) | 
|  | goto _err; | 
|  | err = 0; | 
|  | *value = db_gain; | 
|  | _err: | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int get_switch_ops(snd_mixer_elem_t *elem, int dir, | 
|  | snd_mixer_selem_channel_id_t channel, int *value) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | if (s->selem.caps & SM_CAP_GSWITCH) | 
|  | dir = SM_PLAY; | 
|  | if ((unsigned int) channel >= s->str[dir].channels) | 
|  | return -EINVAL; | 
|  | *value = !!(s->str[dir].sw & (1 << channel)); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int set_volume_ops(snd_mixer_elem_t *elem, int dir, | 
|  | snd_mixer_selem_channel_id_t channel, long value) | 
|  | { | 
|  | int changed; | 
|  | changed = _snd_mixer_selem_set_volume(elem, dir, channel, value); | 
|  | if (changed < 0) | 
|  | return changed; | 
|  | if (changed) | 
|  | return selem_write(elem); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int ask_dB_vol_ops(snd_mixer_elem_t *elem, int dir, | 
|  | long dbValue, long *value, int xdir) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | selem_ctl_t *c; | 
|  |  | 
|  | if (s->selem.caps & SM_CAP_GVOLUME) | 
|  | dir = SM_PLAY; | 
|  | c = get_selem_ctl(s, dir); | 
|  | if (! c) | 
|  | return -EINVAL; | 
|  | return convert_from_dB(c->elem, &s->str[dir], dbValue, value, xdir); | 
|  | } | 
|  |  | 
|  | static int set_dB_ops(snd_mixer_elem_t *elem, int dir, | 
|  | snd_mixer_selem_channel_id_t channel, | 
|  | long db_gain, int xdir) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | selem_ctl_t *c; | 
|  | long value; | 
|  | int err; | 
|  |  | 
|  | if (s->selem.caps & SM_CAP_GVOLUME) | 
|  | dir = SM_PLAY; | 
|  | c = get_selem_ctl(s, dir); | 
|  | if (! c) | 
|  | return -EINVAL; | 
|  | err = convert_from_dB(c->elem, &s->str[dir], db_gain, &value, xdir); | 
|  | if (err < 0) | 
|  | return err; | 
|  | return set_volume_ops(elem, dir, channel, value); | 
|  | } | 
|  |  | 
|  | static int set_switch_ops(snd_mixer_elem_t *elem, int dir, | 
|  | snd_mixer_selem_channel_id_t channel, int value) | 
|  | { | 
|  | int changed; | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | if (s->selem.caps & SM_CAP_GSWITCH) | 
|  | dir = SM_PLAY; | 
|  | if (dir == SM_PLAY) { | 
|  | if (! (s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH))) | 
|  | return -EINVAL; | 
|  | } else { | 
|  | if (! (s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_CSWITCH))) | 
|  | return -EINVAL; | 
|  | } | 
|  | changed = _snd_mixer_selem_set_switch(elem, dir, channel, value); | 
|  | if (changed < 0) | 
|  | return changed; | 
|  | if (changed) | 
|  | return selem_write(elem); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int enum_item_name_ops(snd_mixer_elem_t *elem, | 
|  | unsigned int item, | 
|  | size_t maxlen, char *buf) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | snd_ctl_elem_info_t info = {0}; | 
|  | snd_hctl_elem_t *helem; | 
|  | int type; | 
|  |  | 
|  | type = CTL_GLOBAL_ENUM; | 
|  | helem = s->ctls[type].elem; | 
|  | if (!helem) { | 
|  | type = CTL_PLAYBACK_ENUM; | 
|  | helem = s->ctls[type].elem; | 
|  | } | 
|  | if (!helem) { | 
|  | type = CTL_CAPTURE_ENUM; | 
|  | helem = s->ctls[type].elem; | 
|  | } | 
|  | assert(helem); | 
|  | if (item >= (unsigned int)s->ctls[type].max) | 
|  | return -EINVAL; | 
|  | snd_hctl_elem_info(helem, &info); | 
|  | snd_ctl_elem_info_set_item(&info, item); | 
|  | snd_hctl_elem_info(helem, &info); | 
|  | strncpy(buf, snd_ctl_elem_info_get_item_name(&info), maxlen); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int get_enum_item_ops(snd_mixer_elem_t *elem, | 
|  | snd_mixer_selem_channel_id_t channel, | 
|  | unsigned int *itemp) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | snd_hctl_elem_t *helem; | 
|  | int err; | 
|  |  | 
|  | if ((unsigned int) channel >= s->str[0].channels) | 
|  | return -EINVAL; | 
|  | helem = s->ctls[CTL_GLOBAL_ENUM].elem; | 
|  | if (!helem) helem = s->ctls[CTL_PLAYBACK_ENUM].elem; | 
|  | if (!helem) helem = s->ctls[CTL_CAPTURE_ENUM].elem; | 
|  | assert(helem); | 
|  | err = snd_hctl_elem_read(helem, &ctl); | 
|  | if (! err) | 
|  | *itemp = snd_ctl_elem_value_get_enumerated(&ctl, channel); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int set_enum_item_ops(snd_mixer_elem_t *elem, | 
|  | snd_mixer_selem_channel_id_t channel, | 
|  | unsigned int item) | 
|  | { | 
|  | selem_none_t *s = snd_mixer_elem_get_private(elem); | 
|  | snd_ctl_elem_value_t ctl = {0}; | 
|  | snd_hctl_elem_t *helem; | 
|  | int err; | 
|  | int type; | 
|  |  | 
|  | if ((unsigned int) channel >= s->str[0].channels) { | 
|  | return -EINVAL; | 
|  | } | 
|  | type = CTL_GLOBAL_ENUM; | 
|  | helem = s->ctls[type].elem; | 
|  | if (!helem) { | 
|  | type = CTL_PLAYBACK_ENUM; | 
|  | helem = s->ctls[type].elem; | 
|  | } | 
|  | if (!helem) { | 
|  | type = CTL_CAPTURE_ENUM; | 
|  | helem = s->ctls[type].elem; | 
|  | } | 
|  | assert(helem); | 
|  | if (item >= (unsigned int)s->ctls[type].max) { | 
|  | return -EINVAL; | 
|  | } | 
|  | err = snd_hctl_elem_read(helem, &ctl); | 
|  | if (err < 0) { | 
|  | return err; | 
|  | } | 
|  | snd_ctl_elem_value_set_enumerated(&ctl, channel, item); | 
|  | return snd_hctl_elem_write(helem, &ctl); | 
|  | } | 
|  |  | 
|  | static struct sm_elem_ops simple_none_ops = { | 
|  | .is		= is_ops, | 
|  | .get_range	= get_range_ops, | 
|  | .get_dB_range	= get_dB_range_ops, | 
|  | .set_range	= set_range_ops, | 
|  | .ask_vol_dB	= ask_vol_dB_ops, | 
|  | .ask_dB_vol	= ask_dB_vol_ops, | 
|  | .get_volume	= get_volume_ops, | 
|  | .get_dB		= get_dB_ops, | 
|  | .set_volume	= set_volume_ops, | 
|  | .set_dB		= set_dB_ops, | 
|  | .get_switch	= get_switch_ops, | 
|  | .set_switch	= set_switch_ops, | 
|  | .enum_item_name	= enum_item_name_ops, | 
|  | .get_enum_item	= get_enum_item_ops, | 
|  | .set_enum_item	= set_enum_item_ops | 
|  | }; | 
|  |  | 
|  | static int simple_add1(snd_mixer_class_t *class, const char *name, | 
|  | snd_hctl_elem_t *helem, selem_ctl_type_t type, | 
|  | unsigned int value) | 
|  | { | 
|  | snd_mixer_elem_t *melem; | 
|  | snd_mixer_selem_id_t *id; | 
|  | int new = 0; | 
|  | int err; | 
|  | snd_ctl_elem_info_t info = {0}; | 
|  | selem_none_t *simple; | 
|  | const char *name1; | 
|  | snd_ctl_elem_type_t ctype; | 
|  | unsigned long values; | 
|  |  | 
|  | err = snd_hctl_elem_info(helem, &info); | 
|  | if (err < 0) | 
|  | return err; | 
|  | ctype = snd_ctl_elem_info_get_type(&info); | 
|  | values = snd_ctl_elem_info_get_count(&info); | 
|  | switch (type) { | 
|  | case CTL_SINGLE: | 
|  | if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED) | 
|  | type = CTL_GLOBAL_ENUM; | 
|  | else if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN && | 
|  | ctype != SND_CTL_ELEM_TYPE_INTEGER) | 
|  | return 0; | 
|  | break; | 
|  | case CTL_GLOBAL_ROUTE: | 
|  | case CTL_PLAYBACK_ROUTE: | 
|  | case CTL_CAPTURE_ROUTE: | 
|  | { | 
|  | unsigned int n; | 
|  | if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED) { | 
|  | if (type == CTL_PLAYBACK_ROUTE) | 
|  | type = CTL_PLAYBACK_ENUM; | 
|  | else if (type == CTL_CAPTURE_ROUTE) | 
|  | type = CTL_CAPTURE_ENUM; | 
|  | else | 
|  | type = CTL_GLOBAL_ENUM; | 
|  | break; | 
|  | } | 
|  | if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN) | 
|  | return 0; | 
|  | #ifdef HAVE_SOFT_FLOAT | 
|  | /* up to 256 channels */ | 
|  | for (n = 1; n < 256; n++) | 
|  | if (n * n == values) | 
|  | break; | 
|  | #else | 
|  | n = sqrt((double)values); | 
|  | #endif | 
|  | if (n * n != values) | 
|  | return 0; | 
|  | values = n; | 
|  | break; | 
|  | } | 
|  | case CTL_GLOBAL_SWITCH: | 
|  | case CTL_PLAYBACK_SWITCH: | 
|  | case CTL_CAPTURE_SWITCH: | 
|  | if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED) { | 
|  | if (type == CTL_PLAYBACK_SWITCH) | 
|  | type = CTL_PLAYBACK_ENUM; | 
|  | else if (type == CTL_CAPTURE_SWITCH) | 
|  | type = CTL_CAPTURE_ENUM; | 
|  | else | 
|  | type = CTL_GLOBAL_ENUM; | 
|  | break; | 
|  | } | 
|  | if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN) | 
|  | return 0; | 
|  | break; | 
|  | case CTL_GLOBAL_VOLUME: | 
|  | case CTL_PLAYBACK_VOLUME: | 
|  | case CTL_CAPTURE_VOLUME: | 
|  | if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED) { | 
|  | if (type == CTL_PLAYBACK_VOLUME) | 
|  | type = CTL_PLAYBACK_ENUM; | 
|  | else if (type == CTL_CAPTURE_VOLUME) | 
|  | type = CTL_CAPTURE_ENUM; | 
|  | else | 
|  | type = CTL_GLOBAL_ENUM; | 
|  | break; | 
|  | } | 
|  | if (ctype != SND_CTL_ELEM_TYPE_INTEGER) | 
|  | return 0; | 
|  | break; | 
|  | case CTL_CAPTURE_SOURCE: | 
|  | if (ctype != SND_CTL_ELEM_TYPE_ENUMERATED) | 
|  | return 0; | 
|  | break; | 
|  | case CTL_GLOBAL_ENUM: | 
|  | case CTL_PLAYBACK_ENUM: | 
|  | case CTL_CAPTURE_ENUM: | 
|  | if (ctype != SND_CTL_ELEM_TYPE_ENUMERATED) | 
|  | return 0; | 
|  | break; | 
|  | default: | 
|  | assert(0); | 
|  | break; | 
|  | } | 
|  | name1 = get_short_name(name); | 
|  | if (snd_mixer_selem_id_malloc(&id)) | 
|  | return -ENOMEM; | 
|  | snd_mixer_selem_id_set_name(id, name1); | 
|  | snd_mixer_selem_id_set_index(id, snd_hctl_elem_get_index(helem)); | 
|  | melem = snd_mixer_find_selem(snd_mixer_class_get_mixer(class), id); | 
|  | if (!melem) { | 
|  | simple = calloc(1, sizeof(*simple)); | 
|  | if (!simple) { | 
|  | snd_mixer_selem_id_free(id); | 
|  | return -ENOMEM; | 
|  | } | 
|  | simple->selem.id = id; | 
|  | simple->selem.ops = &simple_none_ops; | 
|  | err = snd_mixer_elem_new(&melem, SND_MIXER_ELEM_SIMPLE, | 
|  | get_compare_weight( | 
|  | snd_mixer_selem_id_get_name(simple->selem.id), | 
|  | snd_mixer_selem_id_get_index(simple->selem.id)), | 
|  | simple, selem_free); | 
|  | if (err < 0) { | 
|  | snd_mixer_selem_id_free(id); | 
|  | free(simple); | 
|  | return err; | 
|  | } | 
|  | new = 1; | 
|  | } else { | 
|  | simple = snd_mixer_elem_get_private(melem); | 
|  | snd_mixer_selem_id_free(id); | 
|  | } | 
|  | if (simple->ctls[type].elem) { | 
|  | SNDERR("helem (%s,'%s',%u,%u,%u) appears twice or more", | 
|  | snd_ctl_elem_iface_name( | 
|  | snd_hctl_elem_get_interface(helem)), | 
|  | snd_hctl_elem_get_name(helem), | 
|  | snd_hctl_elem_get_index(helem), | 
|  | snd_hctl_elem_get_device(helem), | 
|  | snd_hctl_elem_get_subdevice(helem)); | 
|  | err = -EINVAL; | 
|  | goto __error; | 
|  | } | 
|  | simple->ctls[type].elem = helem; | 
|  | simple->ctls[type].type = snd_ctl_elem_info_get_type(&info); | 
|  | simple->ctls[type].inactive = snd_ctl_elem_info_is_inactive(&info); | 
|  | simple->ctls[type].values = values; | 
|  | if ( (type == CTL_GLOBAL_ENUM) || | 
|  | (type == CTL_PLAYBACK_ENUM) || | 
|  | (type == CTL_CAPTURE_ENUM) ) { | 
|  | simple->ctls[type].min = 0; | 
|  | simple->ctls[type].max = snd_ctl_elem_info_get_items(&info); | 
|  | } else { | 
|  | if (ctype == SND_CTL_ELEM_TYPE_INTEGER) { | 
|  | simple->ctls[type].min = | 
|  | snd_ctl_elem_info_get_min(&info); | 
|  | simple->ctls[type].max = | 
|  | snd_ctl_elem_info_get_max(&info); | 
|  | } | 
|  | } | 
|  | switch (type) { | 
|  | case CTL_CAPTURE_SOURCE: | 
|  | simple->capture_item = value; | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | err = snd_mixer_elem_attach(melem, helem); | 
|  | if (err < 0) | 
|  | goto __error; | 
|  | err = simple_update(melem); | 
|  | if (err < 0) { | 
|  | if (new) | 
|  | goto __error; | 
|  | return err; | 
|  | } | 
|  | if (new) | 
|  | err = snd_mixer_elem_add(melem, class); | 
|  | else | 
|  | err = snd_mixer_elem_info(melem); | 
|  | if (err < 0) | 
|  | return err; | 
|  | err = selem_read(melem); | 
|  | if (err < 0) | 
|  | return err; | 
|  | if (err) | 
|  | err = snd_mixer_elem_value(melem); | 
|  | return err; | 
|  | __error: | 
|  | if (new) | 
|  | snd_mixer_elem_free(melem); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static int simple_event_add(snd_mixer_class_t *class, snd_hctl_elem_t *helem) | 
|  | { | 
|  | const char *name = snd_hctl_elem_get_name(helem); | 
|  | size_t len; | 
|  | selem_ctl_type_t type = CTL_SINGLE; /* to shut up warning */ | 
|  | if (snd_hctl_elem_get_interface(helem) != SND_CTL_ELEM_IFACE_MIXER) | 
|  | return 0; | 
|  | if (strcmp(name, "Capture Source") == 0) { | 
|  | snd_ctl_elem_info_t info = {0}; | 
|  | unsigned int k, items; | 
|  | int err; | 
|  | err = snd_hctl_elem_info(helem, &info); | 
|  | assert(err >= 0); | 
|  | if (snd_ctl_elem_info_get_type(&info) != | 
|  | SND_CTL_ELEM_TYPE_ENUMERATED) | 
|  | return 0; | 
|  | items = snd_ctl_elem_info_get_items(&info); | 
|  | for (k = 0; k < items; ++k) { | 
|  | const char *n; | 
|  | snd_ctl_elem_info_set_item(&info, k); | 
|  | err = snd_hctl_elem_info(helem, &info); | 
|  | if (err < 0) | 
|  | return err; | 
|  | n = snd_ctl_elem_info_get_item_name(&info); | 
|  | err = simple_add1(class, n, helem, CTL_CAPTURE_SOURCE, | 
|  | k); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  | len = base_len(name, &type); | 
|  | if (len == 0) { | 
|  | return simple_add1(class, name, helem, CTL_SINGLE, 0); | 
|  | } else { | 
|  | char ename[128]; | 
|  | if (len >= sizeof(ename)) | 
|  | len = sizeof(ename) - 1; | 
|  | memcpy(ename, name, len); | 
|  | ename[len] = 0; | 
|  | /* exception: Capture Volume and Capture Switch */ | 
|  | if (type == CTL_GLOBAL_VOLUME && !strcmp(ename, "Capture")) | 
|  | type = CTL_CAPTURE_VOLUME; | 
|  | else if (type == CTL_GLOBAL_SWITCH && !strcmp(ename, "Capture")) | 
|  | type = CTL_CAPTURE_SWITCH; | 
|  | return simple_add1(class, ename, helem, type, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int simple_event_remove(snd_hctl_elem_t *helem, | 
|  | snd_mixer_elem_t *melem) | 
|  | { | 
|  | selem_none_t *simple = snd_mixer_elem_get_private(melem); | 
|  | int err; | 
|  | int k; | 
|  | for (k = 0; k <= CTL_LAST; k++) { | 
|  | if (simple->ctls[k].elem == helem) | 
|  | break; | 
|  | } | 
|  | assert(k <= CTL_LAST); | 
|  | simple->ctls[k].elem = NULL; | 
|  | err = snd_mixer_elem_detach(melem, helem); | 
|  | if (err < 0) | 
|  | return err; | 
|  | if (snd_mixer_elem_empty(melem)) | 
|  | return snd_mixer_elem_remove(melem); | 
|  | err = simple_update(melem); | 
|  | return snd_mixer_elem_info(melem); | 
|  | } | 
|  |  | 
|  | static int simple_event(snd_mixer_class_t *class, unsigned int mask, | 
|  | snd_hctl_elem_t *helem, snd_mixer_elem_t *melem) | 
|  | { | 
|  | int err; | 
|  | if (mask == SND_CTL_EVENT_MASK_REMOVE) | 
|  | return simple_event_remove(helem, melem); | 
|  | if (mask & SND_CTL_EVENT_MASK_ADD) { | 
|  | err = simple_event_add(class, helem); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | if (mask & SND_CTL_EVENT_MASK_INFO) { | 
|  | err = simple_event_remove(helem, melem); | 
|  | if (err < 0) | 
|  | return err; | 
|  | err = simple_event_add(class, helem); | 
|  | if (err < 0) | 
|  | return err; | 
|  | return 0; | 
|  | } | 
|  | if (mask & SND_CTL_EVENT_MASK_VALUE) { | 
|  | err = selem_read(melem); | 
|  | if (err < 0) | 
|  | return err; | 
|  | if (err) { | 
|  | err = snd_mixer_elem_value(melem); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * \brief Register mixer simple element class - none abstraction | 
|  | * \param mixer Mixer handle | 
|  | * \param options Options container | 
|  | * \param classp Pointer to returned mixer simple element class handle (or NULL) | 
|  | * \return 0 on success otherwise a negative error code | 
|  | */ | 
|  | int snd_mixer_simple_none_register(snd_mixer_t *mixer, | 
|  | struct snd_mixer_selem_regopt *options ATTRIBUTE_UNUSED, | 
|  | snd_mixer_class_t **classp) | 
|  | { | 
|  | snd_mixer_class_t *class; | 
|  | int err; | 
|  |  | 
|  | if (snd_mixer_class_malloc(&class)) | 
|  | return -ENOMEM; | 
|  | snd_mixer_class_set_event(class, simple_event); | 
|  | snd_mixer_class_set_compare(class, snd_mixer_selem_compare); | 
|  | err = snd_mixer_class_register(class, mixer); | 
|  | if (err < 0) { | 
|  | free(class); | 
|  | return err; | 
|  | } | 
|  | if (classp) | 
|  | *classp = class; | 
|  | return 0; | 
|  | } |