xref: /third_party/alsa-utils/amixer/amixer.c (revision c72fcc34)
1/*
2 *   ALSA command line mixer utility
3 *   Copyright (c) 1999-2000 by Jaroslav Kysela <perex@perex.cz>
4 *
5 *   This program is free software; you can redistribute it and/or modify
6 *   it under the terms of the GNU General Public License as published by
7 *   the Free Software Foundation; either version 2 of the License, or
8 *   (at your option) any later version.
9 *
10 *   This program is distributed in the hope that it will be useful,
11 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 *   GNU General Public License for more details.
14 *
15 *   You should have received a copy of the GNU General Public License
16 *   along with this program; if not, write to the Free Software
17 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 *
19 */
20
21#include "aconfig.h"
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <getopt.h>
26#include <stdarg.h>
27#include <ctype.h>
28#include <math.h>
29#include <errno.h>
30#include <assert.h>
31#include <alsa/asoundlib.h>
32#include <poll.h>
33#include <stdint.h>
34#include "amixer.h"
35#include "../alsamixer/volume_mapping.h"
36
37#define LEVEL_BASIC		(1<<0)
38#define LEVEL_INACTIVE		(1<<1)
39#define LEVEL_ID		(1<<2)
40
41static int quiet = 0;
42static int debugflag = 0;
43static int no_check = 0;
44static int smixer_level = 0;
45static int ignore_error = 0;
46static struct snd_mixer_selem_regopt smixer_options;
47static char card[64] = "default";
48
49static void error(const char *fmt,...)
50{
51	va_list va;
52
53	va_start(va, fmt);
54	fprintf(stderr, "amixer: ");
55	vfprintf(stderr, fmt, va);
56	fprintf(stderr, "\n");
57	va_end(va);
58}
59
60static int help(void)
61{
62	printf("Usage: amixer <options> [command]\n");
63	printf("\nAvailable options:\n");
64	printf("  -h,--help       this help\n");
65	printf("  -c,--card N     select the card\n");
66	printf("  -D,--device N   select the device, default '%s'\n", card);
67	printf("  -d,--debug      debug mode\n");
68	printf("  -n,--nocheck    do not perform range checking\n");
69	printf("  -v,--version    print version of this program\n");
70	printf("  -q,--quiet      be quiet\n");
71	printf("  -i,--inactive   show also inactive controls\n");
72	printf("  -a,--abstract L select abstraction level (none or basic)\n");
73	printf("  -s,--stdin      Read and execute commands from stdin sequentially\n");
74	printf("  -R,--raw-volume Use the raw value (default)\n");
75	printf("  -M,--mapped-volume Use the mapped volume\n");
76	printf("\nAvailable commands:\n");
77	printf("  scontrols       show all mixer simple controls\n");
78	printf("  scontents	  show contents of all mixer simple controls (default command)\n");
79	printf("  sset sID P      set contents for one mixer simple control\n");
80	printf("  sget sID        get contents for one mixer simple control\n");
81	printf("  controls        show all controls for given card\n");
82	printf("  contents        show contents of all controls for given card\n");
83	printf("  cset cID P      set control contents for one control\n");
84	printf("  cget cID        get control contents for one control\n");
85	printf("\nAvailable advanced commands:\n");
86	printf("  sevents	  show the mixer events for simple controls\n");
87	printf("  events	  show the mixer events for controls\n");
88	return 0;
89}
90
91static int info(void)
92{
93	int err;
94	snd_ctl_t *handle;
95	snd_mixer_t *mhandle;
96	snd_ctl_card_info_t *info;
97	snd_ctl_elem_list_t *clist;
98	snd_ctl_card_info_alloca(&info);
99	snd_ctl_elem_list_alloca(&clist);
100
101	if ((err = snd_ctl_open(&handle, card, 0)) < 0) {
102		error("Control device %s open error: %s", card, snd_strerror(err));
103		return err;
104	}
105
106	if ((err = snd_ctl_card_info(handle, info)) < 0) {
107		error("Control device %s hw info error: %s", card, snd_strerror(err));
108		return err;
109	}
110	printf("Card %s '%s'/'%s'\n", card, snd_ctl_card_info_get_id(info),
111	       snd_ctl_card_info_get_longname(info));
112	printf("  Mixer name	: '%s'\n", snd_ctl_card_info_get_mixername(info));
113	printf("  Components	: '%s'\n", snd_ctl_card_info_get_components(info));
114	if ((err = snd_ctl_elem_list(handle, clist)) < 0) {
115		error("snd_ctl_elem_list failure: %s", snd_strerror(err));
116	} else {
117		printf("  Controls      : %i\n", snd_ctl_elem_list_get_count(clist));
118	}
119	snd_ctl_close(handle);
120	if ((err = snd_mixer_open(&mhandle, 0)) < 0) {
121		error("Mixer open error: %s", snd_strerror(err));
122		return err;
123	}
124	if (smixer_level == 0 && (err = snd_mixer_attach(mhandle, card)) < 0) {
125		error("Mixer attach %s error: %s", card, snd_strerror(err));
126		snd_mixer_close(mhandle);
127		return err;
128	}
129	if ((err = snd_mixer_selem_register(mhandle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) {
130		error("Mixer register error: %s", snd_strerror(err));
131		snd_mixer_close(mhandle);
132		return err;
133	}
134	err = snd_mixer_load(mhandle);
135	if (err < 0) {
136		error("Mixer load %s error: %s", card, snd_strerror(err));
137		snd_mixer_close(mhandle);
138		return err;
139	}
140	printf("  Simple ctrls  : %i\n", snd_mixer_get_count(mhandle));
141	snd_mixer_close(mhandle);
142	return 0;
143}
144
145static const char *control_type(snd_ctl_elem_info_t *info)
146{
147	return snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(info));
148}
149
150static const char *control_access(snd_ctl_elem_info_t *info)
151{
152	static char result[10];
153	char *res = result;
154
155	*res++ = snd_ctl_elem_info_is_readable(info) ? 'r' : '-';
156	*res++ = snd_ctl_elem_info_is_writable(info) ? 'w' : '-';
157	*res++ = snd_ctl_elem_info_is_inactive(info) ? 'i' : '-';
158	*res++ = snd_ctl_elem_info_is_volatile(info) ? 'v' : '-';
159	*res++ = snd_ctl_elem_info_is_locked(info) ? 'l' : '-';
160	*res++ = snd_ctl_elem_info_is_tlv_readable(info) ? 'R' : '-';
161	*res++ = snd_ctl_elem_info_is_tlv_writable(info) ? 'W' : '-';
162	*res++ = snd_ctl_elem_info_is_tlv_commandable(info) ? 'C' : '-';
163	*res++ = '\0';
164	return result;
165}
166
167#define check_range(val, min, max) \
168	(no_check ? (val) : ((val < min) ? (min) : (val > max) ? (max) : (val)))
169#if 0
170static int convert_range(int val, int omin, int omax, int nmin, int nmax)
171{
172	int orange = omax - omin, nrange = nmax - nmin;
173
174	if (orange == 0)
175		return 0;
176	return rint((((double)nrange * ((double)val - (double)omin)) + ((double)orange / 2.0)) / ((double)orange + (double)nmin));
177}
178#endif
179
180#if 0
181static int convert_db_range(int val, int omin, int omax, int nmin, int nmax)
182{
183	int orange = omax - omin, nrange = nmax - nmin;
184
185	if (orange == 0)
186		return 0;
187	return rint((((double)nrange * ((double)val - (double)omin)) + ((double)orange / 2.0)) / (double)orange + (double)nmin);
188}
189#endif
190
191/* Fuction to convert from volume to percentage. val = volume */
192
193static int convert_prange(long val, long min, long max)
194{
195	long range = max - min;
196	int tmp;
197
198	if (range == 0)
199		return min;
200	val -= min;
201	tmp = rint((double)val/(double)range * 100);
202	return tmp;
203}
204
205/* Function to convert from percentage to volume. perc = percentage */
206static long convert_prange1(long perc, long min, long max)
207{
208	long tmp;
209
210	tmp = rint((double)perc * (double)(max - min) * 0.01);
211	if (tmp == 0 && perc > 0)
212		tmp++;
213	return tmp + min;
214}
215
216struct volume_ops {
217	int (*get_range)(snd_mixer_elem_t *elem, long *min, long *max);
218	int (*get)(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t c,
219		   long *value);
220	int (*set)(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t c,
221		   long value, int dir);
222};
223
224enum { VOL_RAW, VOL_DB, VOL_MAP };
225
226struct volume_ops_set {
227	int (*has_volume)(snd_mixer_elem_t *elem);
228	struct volume_ops v[3];
229};
230
231static int set_playback_dB(snd_mixer_elem_t *elem,
232			   snd_mixer_selem_channel_id_t c, long value, int dir)
233{
234	return snd_mixer_selem_set_playback_dB(elem, c, value, dir);
235}
236
237static int set_capture_dB(snd_mixer_elem_t *elem,
238			  snd_mixer_selem_channel_id_t c, long value, int dir)
239{
240	return snd_mixer_selem_set_capture_dB(elem, c, value, dir);
241}
242
243static int set_playback_raw_volume(snd_mixer_elem_t *elem,
244				   snd_mixer_selem_channel_id_t c,
245				   long value, int dir ATTRIBUTE_UNUSED)
246{
247	return snd_mixer_selem_set_playback_volume(elem, c, value);
248}
249
250static int set_capture_raw_volume(snd_mixer_elem_t *elem,
251				  snd_mixer_selem_channel_id_t c,
252				  long value, int dir ATTRIBUTE_UNUSED)
253{
254	return snd_mixer_selem_set_capture_volume(elem, c, value);
255}
256
257/* FIXME: normalize to int32 space to be compatible with other types */
258#define MAP_VOL_RES	(INT32_MAX / 100)
259
260static int get_mapped_volume_range(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
261				   long *pmin, long *pmax)
262{
263	*pmin = 0;
264	*pmax = MAP_VOL_RES;
265	return 0;
266}
267
268static int get_playback_mapped_volume(snd_mixer_elem_t *elem,
269				      snd_mixer_selem_channel_id_t c,
270				      long *value)
271{
272	*value = (rint)(get_normalized_playback_volume(elem, c) * MAP_VOL_RES);
273	return 0;
274}
275
276static int set_playback_mapped_volume(snd_mixer_elem_t *elem,
277				      snd_mixer_selem_channel_id_t c,
278				      long value, int dir)
279{
280	return set_normalized_playback_volume(elem, c,
281					      (double)value / MAP_VOL_RES, dir);
282}
283
284static int get_capture_mapped_volume(snd_mixer_elem_t *elem,
285				     snd_mixer_selem_channel_id_t c,
286				     long *value)
287{
288	*value = (rint)(get_normalized_capture_volume(elem, c) * MAP_VOL_RES);
289	return 0;
290}
291
292static int set_capture_mapped_volume(snd_mixer_elem_t *elem,
293				     snd_mixer_selem_channel_id_t c,
294				     long value, int dir)
295{
296	return set_normalized_capture_volume(elem, c,
297					     (double)value / MAP_VOL_RES, dir);
298}
299
300static const struct volume_ops_set vol_ops[2] = {
301	{
302		.has_volume = snd_mixer_selem_has_playback_volume,
303		.v = {{ snd_mixer_selem_get_playback_volume_range,
304			snd_mixer_selem_get_playback_volume,
305			set_playback_raw_volume },
306		      { snd_mixer_selem_get_playback_dB_range,
307			snd_mixer_selem_get_playback_dB,
308			set_playback_dB },
309		      { get_mapped_volume_range,
310			get_playback_mapped_volume,
311			set_playback_mapped_volume },
312		},
313	},
314	{
315		.has_volume = snd_mixer_selem_has_capture_volume,
316		.v = {{ snd_mixer_selem_get_capture_volume_range,
317			snd_mixer_selem_get_capture_volume,
318			set_capture_raw_volume },
319		      { snd_mixer_selem_get_capture_dB_range,
320			snd_mixer_selem_get_capture_dB,
321			set_capture_dB },
322		      { get_mapped_volume_range,
323			get_capture_mapped_volume,
324			set_capture_mapped_volume },
325		},
326	},
327};
328
329static int std_vol_type = VOL_RAW;
330
331static int set_volume_simple(snd_mixer_elem_t *elem,
332			     snd_mixer_selem_channel_id_t chn,
333			     char **ptr, int dir)
334{
335	long val, orig, pmin, pmax;
336	char *p = *ptr, *s;
337	int invalid = 0, percent = 0, err = 0;
338	int vol_type;
339	double scale = 1.0;
340	int correct = 0;
341
342	if (! vol_ops[dir].has_volume(elem))
343		invalid = 1;
344
345	if (*p == ':')
346		p++;
347	if (*p == '\0' || (!isdigit(*p) && *p != '-'))
348		goto skip;
349
350	s = p;
351	val = strtol(s, &p, 10);
352	if (*p == '.') {
353		p++;
354		strtol(p, &p, 10);
355	}
356	if (*p == '%') {
357		vol_type = std_vol_type;
358		percent = 1;
359		p++;
360	} else if (toupper(p[0]) == 'D' && toupper(p[1]) == 'B') {
361		vol_type = VOL_DB;
362		p += 2;
363		scale = 100;
364	} else {
365		vol_type = VOL_RAW;
366	}
367
368	if (*p && !strchr(",:+-", *p))
369		invalid = 1;
370
371	val = (long)(strtod(s, NULL) * scale);
372	if (vol_ops[dir].v[vol_type].get_range(elem, &pmin, &pmax) < 0)
373		invalid = 1;
374	if (percent)
375		val = (long)convert_prange1(val, pmin, pmax);
376	if (*p == '+' || *p == '-') {
377		if (! invalid) {
378			if (vol_ops[dir].v[vol_type].get(elem, chn, &orig) < 0)
379				invalid = 1;
380			if (*p == '+') {
381				val = orig + val;
382				correct = 1;
383			} else {
384				val = orig - val;
385				correct = -1;
386			}
387		}
388		p++;
389	}
390
391	if (*p && !strchr(",:", *p))
392		invalid = 1;
393
394	if (! invalid) {
395		val = check_range(val, pmin, pmax);
396		err = vol_ops[dir].v[vol_type].set(elem, chn, val, correct);
397	}
398 skip:
399	if (*p == ',')
400		p++;
401	*ptr = p;
402	return err ? err : (invalid ? -ENOENT : 0);
403}
404
405static int get_bool_simple(char **ptr, char *str, int invert, int orig)
406{
407	if (**ptr == ':')
408		(*ptr)++;
409	if (!strncasecmp(*ptr, str, strlen(str))) {
410		orig = 1 ^ (invert ? 1 : 0);
411		while (**ptr != '\0' && **ptr != ',' && **ptr != ':')
412			(*ptr)++;
413	}
414	if (**ptr == ',' || **ptr == ':')
415		(*ptr)++;
416	return orig;
417}
418
419static int simple_skip_word(char **ptr, char *str)
420{
421	char *xptr = *ptr;
422	if (*xptr == ':')
423		xptr++;
424	if (!strncasecmp(xptr, str, strlen(str))) {
425		while (*xptr != '\0' && *xptr != ',' && *xptr != ':')
426			xptr++;
427		if (*xptr == ',' || *xptr == ':')
428			xptr++;
429		*ptr = xptr;
430		return 1;
431	}
432	return 0;
433}
434
435static void show_control_id(snd_ctl_elem_id_t *id)
436{
437	char *str;
438
439	str = snd_ctl_ascii_elem_id_get(id);
440	if (str)
441		printf("%s", str);
442	free(str);
443}
444
445static void print_spaces(unsigned int spaces)
446{
447	while (spaces-- > 0)
448		putc(' ', stdout);
449}
450
451static void print_dB(long dB)
452{
453	if (dB < 0) {
454		printf("-%li.%02lidB", -dB / 100, -dB % 100);
455	} else {
456		printf("%li.%02lidB", dB / 100, dB % 100);
457	}
458}
459
460static void decode_tlv(unsigned int spaces, unsigned int *tlv, unsigned int tlv_size)
461{
462	unsigned int type = tlv[0];
463	unsigned int size;
464	unsigned int idx = 0;
465	const char *chmap_type = NULL;
466	int lf = 1;
467
468	if (tlv_size < 2 * sizeof(unsigned int)) {
469		printf("TLV size error!\n");
470		return;
471	}
472	print_spaces(spaces);
473	printf("| ");
474	type = tlv[idx++];
475	size = tlv[idx++];
476	tlv_size -= 2 * sizeof(unsigned int);
477	if (size > tlv_size) {
478		printf("TLV size error (%u, %u, %u)!\n", type, size, tlv_size);
479		return;
480	}
481	switch (type) {
482	case SND_CTL_TLVT_CONTAINER:
483		printf("container\n");
484		size += sizeof(unsigned int) -1;
485		size /= sizeof(unsigned int);
486		while (idx < size) {
487			if (tlv[idx+1] > (size - idx) * sizeof(unsigned int)) {
488				printf("TLV size error in compound!\n");
489				return;
490			}
491			decode_tlv(spaces + 2, tlv + idx, tlv[idx+1] + 8);
492			idx += 2 + (tlv[idx+1] + sizeof(unsigned int) - 1) / sizeof(unsigned int);
493		}
494		lf = 0;
495		break;
496	case SND_CTL_TLVT_DB_SCALE:
497		printf("dBscale-");
498		if (size != 2 * sizeof(unsigned int)) {
499			while (size > 0) {
500				printf("0x%08x,", tlv[idx++]);
501				size -= sizeof(unsigned int);
502			}
503		} else {
504			printf("min=");
505			print_dB((int)tlv[2]);
506			printf(",step=");
507			print_dB(tlv[3] & 0xffff);
508			printf(",mute=%i", (tlv[3] >> 16) & 1);
509		}
510		break;
511#ifdef SND_CTL_TLVT_DB_LINEAR
512	case SND_CTL_TLVT_DB_LINEAR:
513		printf("dBlinear-");
514		if (size != 2 * sizeof(unsigned int)) {
515			while (size > 0) {
516				printf("0x%08x,", tlv[idx++]);
517				size -= sizeof(unsigned int);
518			}
519		} else {
520			printf("min=");
521			print_dB((int)tlv[2]);
522			printf(",max=");
523			print_dB((int)tlv[3]);
524		}
525		break;
526#endif
527#ifdef SND_CTL_TLVT_DB_RANGE
528	case SND_CTL_TLVT_DB_RANGE:
529		printf("dBrange-\n");
530		if ((size % (6 * sizeof(unsigned int))) != 0) {
531			while (size > 0) {
532				printf("0x%08x,", tlv[idx++]);
533				size -= sizeof(unsigned int);
534			}
535			break;
536		}
537		while (size > 0) {
538			print_spaces(spaces + 2);
539			printf("rangemin=%i,", tlv[idx++]);
540			printf(",rangemax=%i\n", tlv[idx++]);
541			decode_tlv(spaces + 4, tlv + idx, 4 * sizeof(unsigned int));
542			idx += 4;
543			size -= 6 * sizeof(unsigned int);
544		}
545		break;
546#endif
547#ifdef SND_CTL_TLVT_DB_MINMAX
548	case SND_CTL_TLVT_DB_MINMAX:
549	case SND_CTL_TLVT_DB_MINMAX_MUTE:
550		if (type == SND_CTL_TLVT_DB_MINMAX_MUTE)
551			printf("dBminmaxmute-");
552		else
553			printf("dBminmax-");
554		if (size != 2 * sizeof(unsigned int)) {
555			while (size > 0) {
556				printf("0x%08x,", tlv[idx++]);
557				size -= sizeof(unsigned int);
558			}
559		} else {
560			printf("min=");
561			print_dB((int)tlv[2]);
562			printf(",max=");
563			print_dB((int)tlv[3]);
564		}
565		break;
566#endif
567#ifdef SND_CTL_TLVT_CHMAP_FIXED
568	case SND_CTL_TLVT_CHMAP_FIXED:
569		chmap_type = "fixed";
570		/* Fall through */
571	case SND_CTL_TLVT_CHMAP_VAR:
572		if (!chmap_type)
573			chmap_type = "variable";
574		/* Fall through */
575	case SND_CTL_TLVT_CHMAP_PAIRED:
576		if (!chmap_type)
577			chmap_type = "paired";
578		printf("chmap-%s=", chmap_type);
579
580		while (size > 0) {
581			printf("%s", snd_pcm_chmap_name(tlv[idx++]));
582			size -= sizeof(unsigned int);
583			if (size > 0)
584				printf(",");
585		}
586		break;
587#endif
588	default:
589		printf("unk-%u-", type);
590		while (size > 0) {
591			printf("0x%08x,", tlv[idx++]);
592			size -= sizeof(unsigned int);
593		}
594		break;
595	}
596	if (lf)
597		putc('\n', stdout);
598}
599
600static int show_control(const char *space, snd_hctl_elem_t *elem,
601			int level)
602{
603	int err;
604	unsigned int item, idx, count, *tlv;
605	snd_ctl_elem_type_t type;
606	snd_ctl_elem_id_t *id;
607	snd_ctl_elem_info_t *info;
608	snd_ctl_elem_value_t *control;
609	snd_aes_iec958_t iec958;
610	snd_ctl_elem_id_alloca(&id);
611	snd_ctl_elem_info_alloca(&info);
612	snd_ctl_elem_value_alloca(&control);
613	if ((err = snd_hctl_elem_info(elem, info)) < 0) {
614		error("Control %s snd_hctl_elem_info error: %s\n", card, snd_strerror(err));
615		return err;
616	}
617	if (level & LEVEL_ID) {
618		snd_hctl_elem_get_id(elem, id);
619		show_control_id(id);
620		printf("\n");
621	}
622	count = snd_ctl_elem_info_get_count(info);
623	type = snd_ctl_elem_info_get_type(info);
624	printf("%s; type=%s,access=%s,values=%u", space, control_type(info), control_access(info), count);
625	switch (type) {
626	case SND_CTL_ELEM_TYPE_INTEGER:
627		printf(",min=%li,max=%li,step=%li\n",
628		       snd_ctl_elem_info_get_min(info),
629		       snd_ctl_elem_info_get_max(info),
630		       snd_ctl_elem_info_get_step(info));
631		break;
632	case SND_CTL_ELEM_TYPE_INTEGER64:
633		printf(",min=%lli,max=%lli,step=%lli\n",
634		       snd_ctl_elem_info_get_min64(info),
635		       snd_ctl_elem_info_get_max64(info),
636		       snd_ctl_elem_info_get_step64(info));
637		break;
638	case SND_CTL_ELEM_TYPE_ENUMERATED:
639	{
640		unsigned int items = snd_ctl_elem_info_get_items(info);
641		printf(",items=%u\n", items);
642		for (item = 0; item < items; item++) {
643			snd_ctl_elem_info_set_item(info, item);
644			if ((err = snd_hctl_elem_info(elem, info)) < 0) {
645				error("Control %s element info error: %s\n", card, snd_strerror(err));
646				return err;
647			}
648			printf("%s; Item #%u '%s'\n", space, item, snd_ctl_elem_info_get_item_name(info));
649		}
650		break;
651	}
652	default:
653		printf("\n");
654		break;
655	}
656	if (level & LEVEL_BASIC) {
657		if (!snd_ctl_elem_info_is_readable(info))
658			goto __skip_read;
659		if ((err = snd_hctl_elem_read(elem, control)) < 0) {
660			error("Control %s element read error: %s\n", card, snd_strerror(err));
661			return err;
662		}
663		printf("%s: values=", space);
664		for (idx = 0; idx < count; idx++) {
665			if (idx > 0)
666				printf(",");
667			switch (type) {
668			case SND_CTL_ELEM_TYPE_BOOLEAN:
669				printf("%s", snd_ctl_elem_value_get_boolean(control, idx) ? "on" : "off");
670				break;
671			case SND_CTL_ELEM_TYPE_INTEGER:
672				printf("%li", snd_ctl_elem_value_get_integer(control, idx));
673				break;
674			case SND_CTL_ELEM_TYPE_INTEGER64:
675				printf("%lli", snd_ctl_elem_value_get_integer64(control, idx));
676				break;
677			case SND_CTL_ELEM_TYPE_ENUMERATED:
678				printf("%u", snd_ctl_elem_value_get_enumerated(control, idx));
679				break;
680			case SND_CTL_ELEM_TYPE_BYTES:
681				printf("0x%02x", snd_ctl_elem_value_get_byte(control, idx));
682				break;
683			case SND_CTL_ELEM_TYPE_IEC958:
684				snd_ctl_elem_value_get_iec958(control, &iec958);
685				printf("[AES0=0x%02x AES1=0x%02x AES2=0x%02x AES3=0x%02x]",
686				       iec958.status[0], iec958.status[1],
687				       iec958.status[2], iec958.status[3]);
688				break;
689			default:
690				printf("?");
691				break;
692			}
693		}
694		printf("\n");
695	      __skip_read:
696		if (!snd_ctl_elem_info_is_tlv_readable(info))
697			goto __skip_tlv;
698		/* skip ASoC ext bytes controls that may have huge binary TLV data */
699		if (type == SND_CTL_ELEM_TYPE_BYTES &&
700				!snd_ctl_elem_info_is_readable(info) &&
701				!snd_ctl_elem_info_is_writable(info)) {
702			printf("%s; ASoC TLV Byte control, skipping bytes dump\n", space);
703			goto __skip_tlv;
704		}
705
706		tlv = malloc(4096);
707		if ((err = snd_hctl_elem_tlv_read(elem, tlv, 4096)) < 0) {
708			error("Control %s element TLV read error: %s\n", card, snd_strerror(err));
709			free(tlv);
710			return err;
711		}
712		decode_tlv(strlen(space), tlv, 4096);
713		free(tlv);
714	}
715      __skip_tlv:
716	return 0;
717}
718
719static int controls(int level)
720{
721	int err;
722	snd_hctl_t *handle;
723	snd_hctl_elem_t *elem;
724	snd_ctl_elem_id_t *id;
725	snd_ctl_elem_info_t *info;
726	snd_ctl_elem_id_alloca(&id);
727	snd_ctl_elem_info_alloca(&info);
728
729	if ((err = snd_hctl_open(&handle, card, 0)) < 0) {
730		error("Control %s open error: %s", card, snd_strerror(err));
731		return err;
732	}
733	if ((err = snd_hctl_load(handle)) < 0) {
734		error("Control %s local error: %s\n", card, snd_strerror(err));
735		return err;
736	}
737	for (elem = snd_hctl_first_elem(handle); elem; elem = snd_hctl_elem_next(elem)) {
738		if ((err = snd_hctl_elem_info(elem, info)) < 0) {
739			error("Control %s snd_hctl_elem_info error: %s\n", card, snd_strerror(err));
740			return err;
741		}
742		if (!(level & LEVEL_INACTIVE) && snd_ctl_elem_info_is_inactive(info))
743			continue;
744		snd_hctl_elem_get_id(elem, id);
745		show_control_id(id);
746		printf("\n");
747		if (level & LEVEL_BASIC)
748			show_control("  ", elem, 1);
749	}
750	snd_hctl_close(handle);
751	return 0;
752}
753
754static void show_selem_volume(snd_mixer_elem_t *elem,
755			      snd_mixer_selem_channel_id_t chn, int dir,
756			      long min, long max)
757{
758	long raw, val;
759	vol_ops[dir].v[VOL_RAW].get(elem, chn, &raw);
760	if (std_vol_type == VOL_RAW)
761		val = convert_prange(raw, min, max);
762	else {
763		vol_ops[dir].v[std_vol_type].get(elem, chn, &val);
764		val = convert_prange(val, 0, MAP_VOL_RES);
765	}
766	printf(" %li [%li%%]", raw, val);
767	if (!vol_ops[dir].v[VOL_DB].get(elem, chn, &val)) {
768		printf(" [");
769		print_dB(val);
770		printf("]");
771	}
772}
773
774static int show_selem(snd_mixer_t *handle, snd_mixer_selem_id_t *id, const char *space, int level)
775{
776	snd_mixer_selem_channel_id_t chn;
777	long pmin = 0, pmax = 0;
778	long cmin = 0, cmax = 0;
779	int psw, csw;
780	int pmono, cmono, mono_ok = 0;
781	snd_mixer_elem_t *elem;
782
783	elem = snd_mixer_find_selem(handle, id);
784	if (!elem) {
785		error("Mixer %s simple element not found", card);
786		return -ENOENT;
787	}
788
789	if (level & LEVEL_BASIC) {
790		printf("%sCapabilities:", space);
791		if (snd_mixer_selem_has_common_volume(elem)) {
792			printf(" volume");
793			if (snd_mixer_selem_has_playback_volume_joined(elem))
794				printf(" volume-joined");
795		} else {
796			if (snd_mixer_selem_has_playback_volume(elem)) {
797				printf(" pvolume");
798				if (snd_mixer_selem_has_playback_volume_joined(elem))
799					printf(" pvolume-joined");
800			}
801			if (snd_mixer_selem_has_capture_volume(elem)) {
802				printf(" cvolume");
803				if (snd_mixer_selem_has_capture_volume_joined(elem))
804					printf(" cvolume-joined");
805			}
806		}
807		if (snd_mixer_selem_has_common_switch(elem)) {
808			printf(" switch");
809			if (snd_mixer_selem_has_playback_switch_joined(elem))
810				printf(" switch-joined");
811		} else {
812			if (snd_mixer_selem_has_playback_switch(elem)) {
813				printf(" pswitch");
814				if (snd_mixer_selem_has_playback_switch_joined(elem))
815					printf(" pswitch-joined");
816			}
817			if (snd_mixer_selem_has_capture_switch(elem)) {
818				printf(" cswitch");
819				if (snd_mixer_selem_has_capture_switch_joined(elem))
820					printf(" cswitch-joined");
821				if (snd_mixer_selem_has_capture_switch_exclusive(elem))
822					printf(" cswitch-exclusive");
823			}
824		}
825		if (snd_mixer_selem_is_enum_playback(elem)) {
826			printf(" penum");
827		} else if (snd_mixer_selem_is_enum_capture(elem)) {
828			printf(" cenum");
829		} else if (snd_mixer_selem_is_enumerated(elem)) {
830			printf(" enum");
831		}
832		printf("\n");
833		if (snd_mixer_selem_is_enumerated(elem)) {
834			int i, items;
835			unsigned int idx;
836			/*
837			 * See snd_ctl_elem_init_enum_names() in
838			 * sound/core/control.c.
839			 */
840			char itemname[64];
841			items = snd_mixer_selem_get_enum_items(elem);
842			printf("  Items:");
843			for (i = 0; i < items; i++) {
844				snd_mixer_selem_get_enum_item_name(elem, i, sizeof(itemname) - 1, itemname);
845				printf(" '%s'", itemname);
846			}
847			printf("\n");
848			for (i = 0; !snd_mixer_selem_get_enum_item(elem, i, &idx); i++) {
849				snd_mixer_selem_get_enum_item_name(elem, idx, sizeof(itemname) - 1, itemname);
850				printf("  Item%d: '%s'\n", i, itemname);
851			}
852			return 0; /* no more thing to do */
853		}
854		if (snd_mixer_selem_has_capture_switch_exclusive(elem))
855			printf("%sCapture exclusive group: %i\n", space,
856			       snd_mixer_selem_get_capture_group(elem));
857		if (snd_mixer_selem_has_playback_volume(elem) ||
858		    snd_mixer_selem_has_playback_switch(elem)) {
859			printf("%sPlayback channels:", space);
860			if (snd_mixer_selem_is_playback_mono(elem)) {
861				printf(" Mono");
862			} else {
863				int first = 1;
864				for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++){
865					if (!snd_mixer_selem_has_playback_channel(elem, chn))
866						continue;
867					if (!first)
868						printf(" -");
869					printf(" %s", snd_mixer_selem_channel_name(chn));
870					first = 0;
871				}
872			}
873			printf("\n");
874		}
875		if (snd_mixer_selem_has_capture_volume(elem) ||
876		    snd_mixer_selem_has_capture_switch(elem)) {
877			printf("%sCapture channels:", space);
878			if (snd_mixer_selem_is_capture_mono(elem)) {
879				printf(" Mono");
880			} else {
881				int first = 1;
882				for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++){
883					if (!snd_mixer_selem_has_capture_channel(elem, chn))
884						continue;
885					if (!first)
886						printf(" -");
887					printf(" %s", snd_mixer_selem_channel_name(chn));
888					first = 0;
889				}
890			}
891			printf("\n");
892		}
893		if (snd_mixer_selem_has_playback_volume(elem) ||
894		    snd_mixer_selem_has_capture_volume(elem)) {
895			printf("%sLimits:", space);
896			if (snd_mixer_selem_has_common_volume(elem)) {
897				snd_mixer_selem_get_playback_volume_range(elem, &pmin, &pmax);
898				snd_mixer_selem_get_capture_volume_range(elem, &cmin, &cmax);
899				printf(" %li - %li", pmin, pmax);
900			} else {
901				if (snd_mixer_selem_has_playback_volume(elem)) {
902					snd_mixer_selem_get_playback_volume_range(elem, &pmin, &pmax);
903					printf(" Playback %li - %li", pmin, pmax);
904				}
905				if (snd_mixer_selem_has_capture_volume(elem)) {
906					snd_mixer_selem_get_capture_volume_range(elem, &cmin, &cmax);
907					printf(" Capture %li - %li", cmin, cmax);
908				}
909			}
910			printf("\n");
911		}
912		pmono = snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_MONO) &&
913		        (snd_mixer_selem_is_playback_mono(elem) ||
914			 (!snd_mixer_selem_has_playback_volume(elem) &&
915			  !snd_mixer_selem_has_playback_switch(elem)));
916		cmono = snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_MONO) &&
917		        (snd_mixer_selem_is_capture_mono(elem) ||
918			 (!snd_mixer_selem_has_capture_volume(elem) &&
919			  !snd_mixer_selem_has_capture_switch(elem)));
920#if 0
921		printf("pmono = %i, cmono = %i (%i, %i, %i, %i)\n", pmono, cmono,
922				snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_MONO),
923				snd_mixer_selem_is_capture_mono(elem),
924				snd_mixer_selem_has_capture_volume(elem),
925				snd_mixer_selem_has_capture_switch(elem));
926#endif
927		if (pmono || cmono) {
928			if (!mono_ok) {
929				printf("%s%s:", space, "Mono");
930				mono_ok = 1;
931			}
932			if (snd_mixer_selem_has_common_volume(elem)) {
933				show_selem_volume(elem, SND_MIXER_SCHN_MONO, 0, pmin, pmax);
934			}
935			if (snd_mixer_selem_has_common_switch(elem)) {
936				snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &psw);
937				printf(" [%s]", psw ? "on" : "off");
938			}
939		}
940		if (pmono && snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_MONO)) {
941			int title = 0;
942			if (!mono_ok) {
943				printf("%s%s:", space, "Mono");
944				mono_ok = 1;
945			}
946			if (!snd_mixer_selem_has_common_volume(elem)) {
947				if (snd_mixer_selem_has_playback_volume(elem)) {
948					printf(" Playback");
949					title = 1;
950					show_selem_volume(elem, SND_MIXER_SCHN_MONO, 0, pmin, pmax);
951				}
952			}
953			if (!snd_mixer_selem_has_common_switch(elem)) {
954				if (snd_mixer_selem_has_playback_switch(elem)) {
955					if (!title)
956						printf(" Playback");
957					snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &psw);
958					printf(" [%s]", psw ? "on" : "off");
959				}
960			}
961		}
962		if (cmono && snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_MONO)) {
963			int title = 0;
964			if (!mono_ok) {
965				printf("%s%s:", space, "Mono");
966				mono_ok = 1;
967			}
968			if (!snd_mixer_selem_has_common_volume(elem)) {
969				if (snd_mixer_selem_has_capture_volume(elem)) {
970					printf(" Capture");
971					title = 1;
972					show_selem_volume(elem, SND_MIXER_SCHN_MONO, 1, cmin, cmax);
973				}
974			}
975			if (!snd_mixer_selem_has_common_switch(elem)) {
976				if (snd_mixer_selem_has_capture_switch(elem)) {
977					if (!title)
978						printf(" Capture");
979					snd_mixer_selem_get_capture_switch(elem, SND_MIXER_SCHN_MONO, &csw);
980					printf(" [%s]", csw ? "on" : "off");
981				}
982			}
983		}
984		if (pmono || cmono)
985			printf("\n");
986		if (!pmono || !cmono) {
987			for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
988				if ((pmono || !snd_mixer_selem_has_playback_channel(elem, chn)) &&
989				    (cmono || !snd_mixer_selem_has_capture_channel(elem, chn)))
990					continue;
991				printf("%s%s:", space, snd_mixer_selem_channel_name(chn));
992				if (!pmono && !cmono && snd_mixer_selem_has_common_volume(elem)) {
993					show_selem_volume(elem, chn, 0, pmin, pmax);
994				}
995				if (!pmono && !cmono && snd_mixer_selem_has_common_switch(elem)) {
996					snd_mixer_selem_get_playback_switch(elem, chn, &psw);
997					printf(" [%s]", psw ? "on" : "off");
998				}
999				if (!pmono && snd_mixer_selem_has_playback_channel(elem, chn)) {
1000					int title = 0;
1001					if (!snd_mixer_selem_has_common_volume(elem)) {
1002						if (snd_mixer_selem_has_playback_volume(elem)) {
1003							printf(" Playback");
1004							title = 1;
1005							show_selem_volume(elem, chn, 0, pmin, pmax);
1006						}
1007					}
1008					if (!snd_mixer_selem_has_common_switch(elem)) {
1009						if (snd_mixer_selem_has_playback_switch(elem)) {
1010							if (!title)
1011								printf(" Playback");
1012							snd_mixer_selem_get_playback_switch(elem, chn, &psw);
1013							printf(" [%s]", psw ? "on" : "off");
1014						}
1015					}
1016				}
1017				if (!cmono && snd_mixer_selem_has_capture_channel(elem, chn)) {
1018					int title = 0;
1019					if (!snd_mixer_selem_has_common_volume(elem)) {
1020						if (snd_mixer_selem_has_capture_volume(elem)) {
1021							printf(" Capture");
1022							title = 1;
1023							show_selem_volume(elem, chn, 1, cmin, cmax);
1024						}
1025					}
1026					if (!snd_mixer_selem_has_common_switch(elem)) {
1027						if (snd_mixer_selem_has_capture_switch(elem)) {
1028							if (!title)
1029								printf(" Capture");
1030							snd_mixer_selem_get_capture_switch(elem, chn, &csw);
1031							printf(" [%s]", csw ? "on" : "off");
1032						}
1033					}
1034				}
1035				printf("\n");
1036			}
1037		}
1038	}
1039	return 0;
1040}
1041
1042static int selems(int level)
1043{
1044	int err;
1045	snd_mixer_t *handle;
1046	snd_mixer_selem_id_t *sid;
1047	snd_mixer_elem_t *elem;
1048	snd_mixer_selem_id_alloca(&sid);
1049
1050	if ((err = snd_mixer_open(&handle, 0)) < 0) {
1051		error("Mixer %s open error: %s", card, snd_strerror(err));
1052		return err;
1053	}
1054	if (smixer_level == 0 && (err = snd_mixer_attach(handle, card)) < 0) {
1055		error("Mixer attach %s error: %s", card, snd_strerror(err));
1056		snd_mixer_close(handle);
1057		return err;
1058	}
1059	if ((err = snd_mixer_selem_register(handle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) {
1060		error("Mixer register error: %s", snd_strerror(err));
1061		snd_mixer_close(handle);
1062		return err;
1063	}
1064	err = snd_mixer_load(handle);
1065	if (err < 0) {
1066		error("Mixer %s load error: %s", card, snd_strerror(err));
1067		snd_mixer_close(handle);
1068		return err;
1069	}
1070	for (elem = snd_mixer_first_elem(handle); elem; elem = snd_mixer_elem_next(elem)) {
1071		snd_mixer_selem_get_id(elem, sid);
1072		if (!(level & LEVEL_INACTIVE) && !snd_mixer_selem_is_active(elem))
1073			continue;
1074		printf("Simple mixer control '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
1075		show_selem(handle, sid, "  ", level);
1076	}
1077	snd_mixer_close(handle);
1078	return 0;
1079}
1080
1081static int parse_simple_id(const char *str, snd_mixer_selem_id_t *sid)
1082{
1083	int c, size;
1084	char buf[128];
1085	char *ptr = buf;
1086
1087	while (*str == ' ' || *str == '\t')
1088		str++;
1089	if (!(*str))
1090		return -EINVAL;
1091	size = 1;	/* for '\0' */
1092	if (*str != '"' && *str != '\'') {
1093		while (*str && *str != ',') {
1094			if (size < (int)sizeof(buf)) {
1095				*ptr++ = *str;
1096				size++;
1097			}
1098			str++;
1099		}
1100	} else {
1101		c = *str++;
1102		while (*str && *str != c) {
1103			if (size < (int)sizeof(buf)) {
1104				*ptr++ = *str;
1105				size++;
1106			}
1107			str++;
1108		}
1109		if (*str == c)
1110			str++;
1111	}
1112	if (*str == '\0') {
1113		snd_mixer_selem_id_set_index(sid, 0);
1114		*ptr = 0;
1115		goto _set;
1116	}
1117	if (*str != ',')
1118		return -EINVAL;
1119	*ptr = 0;	/* terminate the string */
1120	str++;
1121	if (!isdigit(*str))
1122		return -EINVAL;
1123	snd_mixer_selem_id_set_index(sid, atoi(str));
1124       _set:
1125	snd_mixer_selem_id_set_name(sid, buf);
1126	return 0;
1127}
1128
1129static int cset(int argc, char *argv[], int roflag, int keep_handle)
1130{
1131	int err;
1132	static snd_ctl_t *handle = NULL;
1133	snd_ctl_elem_info_t *info;
1134	snd_ctl_elem_id_t *id;
1135	snd_ctl_elem_value_t *control;
1136	snd_ctl_elem_info_alloca(&info);
1137	snd_ctl_elem_id_alloca(&id);
1138	snd_ctl_elem_value_alloca(&control);
1139
1140	if (argc < 1) {
1141		fprintf(stderr, "Specify a full control identifier: [[iface=<iface>,][name='name',][index=<index>,][device=<device>,][subdevice=<subdevice>]]|[numid=<numid>]\n");
1142		return -EINVAL;
1143	}
1144	if (snd_ctl_ascii_elem_id_parse(id, argv[0])) {
1145		fprintf(stderr, "Wrong control identifier: %s\n", argv[0]);
1146		return -EINVAL;
1147	}
1148	if (debugflag) {
1149		printf("VERIFY ID: ");
1150		show_control_id(id);
1151		printf("\n");
1152	}
1153	if (handle == NULL &&
1154	    (err = snd_ctl_open(&handle, card, 0)) < 0) {
1155		error("Control %s open error: %s\n", card, snd_strerror(err));
1156		return err;
1157	}
1158	snd_ctl_elem_info_set_id(info, id);
1159	if ((err = snd_ctl_elem_info(handle, info)) < 0) {
1160		if (ignore_error)
1161			return 0;
1162		error("Cannot find the given element from control %s\n", card);
1163		if (! keep_handle) {
1164			snd_ctl_close(handle);
1165			handle = NULL;
1166		}
1167		return err;
1168	}
1169	snd_ctl_elem_info_get_id(info, id);     /* FIXME: Remove it when hctl find works ok !!! */
1170	if (!roflag) {
1171		snd_ctl_elem_value_set_id(control, id);
1172		if ((err = snd_ctl_elem_read(handle, control)) < 0) {
1173			if (ignore_error)
1174				return 0;
1175			error("Cannot read the given element from control %s\n", card);
1176			if (! keep_handle) {
1177				snd_ctl_close(handle);
1178				handle = NULL;
1179			}
1180			return err;
1181		}
1182		err = snd_ctl_ascii_value_parse(handle, control, info, argv[1]);
1183		if (err < 0) {
1184 			if (!ignore_error)
1185				error("Control %s parse error: %s\n", card, snd_strerror(err));
1186			if (!keep_handle) {
1187				snd_ctl_close(handle);
1188				handle = NULL;
1189			}
1190			return ignore_error ? 0 : err;
1191		}
1192		if ((err = snd_ctl_elem_write(handle, control)) < 0) {
1193			if (!ignore_error)
1194				error("Control %s element write error: %s\n", card, snd_strerror(err));
1195			if (!keep_handle) {
1196				snd_ctl_close(handle);
1197				handle = NULL;
1198			}
1199			return ignore_error ? 0 : err;
1200		}
1201	}
1202	if (! keep_handle) {
1203		snd_ctl_close(handle);
1204		handle = NULL;
1205	}
1206	if (!quiet) {
1207		snd_hctl_t *hctl;
1208		snd_hctl_elem_t *elem;
1209		if ((err = snd_hctl_open(&hctl, card, 0)) < 0) {
1210			error("Control %s open error: %s\n", card, snd_strerror(err));
1211			return err;
1212		}
1213		if ((err = snd_hctl_load(hctl)) < 0) {
1214			error("Control %s load error: %s\n", card, snd_strerror(err));
1215			return err;
1216		}
1217		elem = snd_hctl_find_elem(hctl, id);
1218		if (elem)
1219			show_control("  ", elem, LEVEL_BASIC | LEVEL_ID);
1220		else
1221			printf("Could not find the specified element\n");
1222		snd_hctl_close(hctl);
1223	}
1224	return 0;
1225}
1226
1227typedef struct channel_mask {
1228	char *name;
1229	unsigned int mask;
1230} channel_mask_t;
1231static const channel_mask_t chanmask[] = {
1232	{"frontleft", 1 << SND_MIXER_SCHN_FRONT_LEFT},
1233	{"frontright", 1 << SND_MIXER_SCHN_FRONT_RIGHT},
1234	{"frontcenter", 1 << SND_MIXER_SCHN_FRONT_CENTER},
1235	{"front", ((1 << SND_MIXER_SCHN_FRONT_LEFT) |
1236		   (1 << SND_MIXER_SCHN_FRONT_RIGHT))},
1237	{"center", 1 << SND_MIXER_SCHN_FRONT_CENTER},
1238	{"rearleft", 1 << SND_MIXER_SCHN_REAR_LEFT},
1239	{"rearright", 1 << SND_MIXER_SCHN_REAR_RIGHT},
1240	{"rear", ((1 << SND_MIXER_SCHN_REAR_LEFT) |
1241		  (1 << SND_MIXER_SCHN_REAR_RIGHT))},
1242	{"woofer", 1 << SND_MIXER_SCHN_WOOFER},
1243	{NULL, 0}
1244};
1245
1246static unsigned int channels_mask(char **arg, unsigned int def)
1247{
1248	const channel_mask_t *c;
1249
1250	for (c = chanmask; c->name; c++) {
1251		if (strncasecmp(*arg, c->name, strlen(c->name)) == 0) {
1252			while (**arg != '\0' && **arg != ',' && **arg != ' ' && **arg != '\t')
1253				(*arg)++;
1254			if (**arg == ',' || **arg == ' ' || **arg == '\t')
1255				(*arg)++;
1256			return c->mask;
1257		}
1258	}
1259	return def;
1260}
1261
1262static unsigned int dir_mask(char **arg, unsigned int def)
1263{
1264	int findend = 0;
1265
1266	if (strncasecmp(*arg, "playback", 8) == 0)
1267		def = findend = 1;
1268	else if (strncasecmp(*arg, "capture", 8) == 0)
1269		def = findend = 2;
1270	if (findend) {
1271		while (**arg != '\0' && **arg != ',' && **arg != ' ' && **arg != '\t')
1272			(*arg)++;
1273		if (**arg == ',' || **arg == ' ' || **arg == '\t')
1274			(*arg)++;
1275	}
1276	return def;
1277}
1278
1279static int get_enum_item_index(snd_mixer_elem_t *elem, char **ptrp)
1280{
1281	char *ptr = *ptrp;
1282	int items, i, len;
1283
1284	/* See snd_ctl_elem_init_enum_names() in sound/core/control.c. */
1285	char name[64];
1286
1287	items = snd_mixer_selem_get_enum_items(elem);
1288	if (items <= 0)
1289		return -1;
1290
1291	for (i = 0; i < items; i++) {
1292		if (snd_mixer_selem_get_enum_item_name(elem, i, sizeof(name)-1, name) < 0)
1293			continue;
1294
1295		len = strlen(name);
1296		if (! strncmp(name, ptr, len)) {
1297			if (! ptr[len] || ptr[len] == ',' || ptr[len] == '\n') {
1298				ptr += len;
1299				*ptrp = ptr;
1300				return i;
1301			}
1302		}
1303	}
1304	return -1;
1305}
1306
1307static int sset_enum(snd_mixer_elem_t *elem, unsigned int argc, char **argv)
1308{
1309	unsigned int idx, item = 0;
1310	int check_flag = ignore_error ? 0 : -1;
1311
1312	for (idx = 1; idx < argc; idx++) {
1313		char *ptr = argv[idx];
1314		while (*ptr) {
1315			int ival = get_enum_item_index(elem, &ptr);
1316			if (ival < 0)
1317				return check_flag;
1318			if (snd_mixer_selem_set_enum_item(elem, item++, ival) >= 0)
1319				check_flag = 1;
1320			/* skip separators */
1321			while (*ptr == ',' || isspace(*ptr))
1322				ptr++;
1323		}
1324	}
1325	return check_flag;
1326}
1327
1328static int sset_channels(snd_mixer_elem_t *elem, unsigned int argc, char **argv)
1329{
1330	unsigned int channels = ~0U;
1331	unsigned int dir = 3, okflag = 3;
1332	unsigned int idx;
1333	snd_mixer_selem_channel_id_t chn;
1334	int check_flag = ignore_error ? 0 : -1;
1335
1336	for (idx = 1; idx < argc; idx++) {
1337		char *ptr = argv[idx], *optr;
1338		int multi, firstchn = 1;
1339		channels = channels_mask(&ptr, channels);
1340		if (*ptr == '\0')
1341			continue;
1342		dir = dir_mask(&ptr, dir);
1343		if (*ptr == '\0')
1344			continue;
1345		multi = (strchr(ptr, ',') != NULL);
1346		optr = ptr;
1347		for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
1348			char *sptr = NULL;
1349			int ival;
1350
1351			if (!(channels & (1 << chn)))
1352				continue;
1353
1354			if ((dir & 1) && snd_mixer_selem_has_playback_channel(elem, chn)) {
1355				sptr = ptr;
1356				if (!strncmp(ptr, "mute", 4) && snd_mixer_selem_has_playback_switch(elem)) {
1357					snd_mixer_selem_get_playback_switch(elem, chn, &ival);
1358					if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "mute", 1, ival)) >= 0)
1359						check_flag = 1;
1360				} else if (!strncmp(ptr, "off", 3) && snd_mixer_selem_has_playback_switch(elem)) {
1361					snd_mixer_selem_get_playback_switch(elem, chn, &ival);
1362					if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "off", 1, ival)) >= 0)
1363						check_flag = 1;
1364				} else if (!strncmp(ptr, "unmute", 6) && snd_mixer_selem_has_playback_switch(elem)) {
1365					snd_mixer_selem_get_playback_switch(elem, chn, &ival);
1366					if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "unmute", 0, ival)) >= 0)
1367						check_flag = 1;
1368				} else if (!strncmp(ptr, "on", 2) && snd_mixer_selem_has_playback_switch(elem)) {
1369					snd_mixer_selem_get_playback_switch(elem, chn, &ival);
1370					if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "on", 0, ival)) >= 0)
1371						check_flag = 1;
1372				} else if (!strncmp(ptr, "toggle", 6) && snd_mixer_selem_has_playback_switch(elem)) {
1373					if (firstchn || !snd_mixer_selem_has_playback_switch_joined(elem)) {
1374						snd_mixer_selem_get_playback_switch(elem, chn, &ival);
1375						if (snd_mixer_selem_set_playback_switch(elem, chn, (ival ? 1 : 0) ^ 1) >= 0)
1376							check_flag = 1;
1377					}
1378					simple_skip_word(&ptr, "toggle");
1379				} else if (isdigit(*ptr) || *ptr == '-' || *ptr == '+') {
1380					if (set_volume_simple(elem, chn, &ptr, 0) >= 0)
1381						check_flag = 1;
1382				} else if (simple_skip_word(&ptr, "cap") || simple_skip_word(&ptr, "rec") ||
1383					   simple_skip_word(&ptr, "nocap") || simple_skip_word(&ptr, "norec")) {
1384					/* nothing */
1385				} else {
1386					okflag &= ~1;
1387				}
1388			}
1389			if ((dir & 2) && snd_mixer_selem_has_capture_channel(elem, chn)) {
1390				if (sptr != NULL)
1391					ptr = sptr;
1392				sptr = ptr;
1393				if (!strncmp(ptr, "cap", 3) && snd_mixer_selem_has_capture_switch(elem)) {
1394					snd_mixer_selem_get_capture_switch(elem, chn, &ival);
1395					if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "cap", 0, ival)) >= 0)
1396						check_flag = 1;
1397				} else if (!strncmp(ptr, "rec", 3) && snd_mixer_selem_has_capture_switch(elem)) {
1398					snd_mixer_selem_get_capture_switch(elem, chn, &ival);
1399					if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "rec", 0, ival)) >= 0)
1400						check_flag = 1;
1401				} else if (!strncmp(ptr, "nocap", 5) && snd_mixer_selem_has_capture_switch(elem)) {
1402					snd_mixer_selem_get_capture_switch(elem, chn, &ival);
1403					if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "nocap", 1, ival)) >= 0)
1404						check_flag = 1;
1405				} else if (!strncmp(ptr, "norec", 5) && snd_mixer_selem_has_capture_switch(elem)) {
1406					snd_mixer_selem_get_capture_switch(elem, chn, &ival);
1407					if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "norec", 1, ival)) >= 0)
1408						check_flag = 1;
1409				} else if (!strncmp(ptr, "toggle", 6) && snd_mixer_selem_has_capture_switch(elem)) {
1410					if (firstchn || !snd_mixer_selem_has_capture_switch_joined(elem)) {
1411						snd_mixer_selem_get_capture_switch(elem, chn, &ival);
1412						if (snd_mixer_selem_set_capture_switch(elem, chn, (ival ? 1 : 0) ^ 1) >= 0)
1413							check_flag = 1;
1414					}
1415					simple_skip_word(&ptr, "toggle");
1416				} else if (isdigit(*ptr) || *ptr == '-' || *ptr == '+') {
1417					if (set_volume_simple(elem, chn, &ptr, 1) >= 0)
1418						check_flag = 1;
1419				} else if (simple_skip_word(&ptr, "mute") || simple_skip_word(&ptr, "off") ||
1420					   simple_skip_word(&ptr, "unmute") || simple_skip_word(&ptr, "on")) {
1421					/* nothing */
1422				} else {
1423					okflag &= ~2;
1424				}
1425			}
1426			if (okflag == 0) {
1427				if (debugflag) {
1428					if (dir & 1)
1429						error("Unknown playback setup '%s'..", ptr);
1430					if (dir & 2)
1431						error("Unknown capture setup '%s'..", ptr);
1432				}
1433				return 0; /* just skip it */
1434			}
1435			if (!multi)
1436				ptr = optr;
1437			firstchn = 0;
1438		}
1439	}
1440	return check_flag;
1441}
1442
1443static int sset(unsigned int argc, char *argv[], int roflag, int keep_handle)
1444{
1445	int err = 0;
1446	static snd_mixer_t *handle = NULL;
1447	snd_mixer_elem_t *elem;
1448	snd_mixer_selem_id_t *sid;
1449	snd_mixer_selem_id_alloca(&sid);
1450
1451	if (argc < 1) {
1452		fprintf(stderr, "Specify a scontrol identifier: 'name',index\n");
1453		return 1;
1454	}
1455	if (parse_simple_id(argv[0], sid)) {
1456		fprintf(stderr, "Wrong scontrol identifier: %s\n", argv[0]);
1457		return 1;
1458	}
1459	if (!roflag && argc < 2) {
1460		fprintf(stderr, "Specify what you want to set...\n");
1461		return 1;
1462	}
1463	if (handle == NULL) {
1464		if ((err = snd_mixer_open(&handle, 0)) < 0) {
1465			error("Mixer %s open error: %s\n", card, snd_strerror(err));
1466			return err;
1467		}
1468		if (smixer_level == 0 && (err = snd_mixer_attach(handle, card)) < 0) {
1469			error("Mixer attach %s error: %s", card, snd_strerror(err));
1470			snd_mixer_close(handle);
1471			handle = NULL;
1472			return err;
1473		}
1474		if ((err = snd_mixer_selem_register(handle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) {
1475			error("Mixer register error: %s", snd_strerror(err));
1476			snd_mixer_close(handle);
1477			handle = NULL;
1478			return err;
1479		}
1480		err = snd_mixer_load(handle);
1481		if (err < 0) {
1482			error("Mixer %s load error: %s", card, snd_strerror(err));
1483			snd_mixer_close(handle);
1484			handle = NULL;
1485			return err;
1486		}
1487	}
1488	elem = snd_mixer_find_selem(handle, sid);
1489	if (!elem) {
1490		if (ignore_error)
1491			return 0;
1492		error("Unable to find simple control '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
1493		snd_mixer_close(handle);
1494		handle = NULL;
1495		return -ENOENT;
1496	}
1497	if (!roflag) {
1498		/* enum control */
1499		if (snd_mixer_selem_is_enumerated(elem))
1500			err = sset_enum(elem, argc, argv);
1501		else
1502			err = sset_channels(elem, argc, argv);
1503
1504		if (!err)
1505			goto done;
1506		if (err < 0) {
1507			error("Invalid command!");
1508			goto done;
1509		}
1510	}
1511	if (!quiet) {
1512		printf("Simple mixer control '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
1513		show_selem(handle, sid, "  ", 1);
1514	}
1515 done:
1516	if (! keep_handle) {
1517		snd_mixer_close(handle);
1518		handle = NULL;
1519	}
1520	return err < 0 ? 1 : 0;
1521}
1522
1523static void events_info(snd_hctl_elem_t *helem)
1524{
1525	snd_ctl_elem_id_t *id;
1526	snd_ctl_elem_id_alloca(&id);
1527	snd_hctl_elem_get_id(helem, id);
1528	printf("event info: ");
1529	show_control_id(id);
1530	printf("\n");
1531}
1532
1533static void events_value(snd_hctl_elem_t *helem)
1534{
1535	snd_ctl_elem_id_t *id;
1536	snd_ctl_elem_id_alloca(&id);
1537	snd_hctl_elem_get_id(helem, id);
1538	printf("event value: ");
1539	show_control_id(id);
1540	printf("\n");
1541}
1542
1543static void events_remove(snd_hctl_elem_t *helem)
1544{
1545	snd_ctl_elem_id_t *id;
1546	snd_ctl_elem_id_alloca(&id);
1547	snd_hctl_elem_get_id(helem, id);
1548	printf("event remove: ");
1549	show_control_id(id);
1550	printf("\n");
1551}
1552
1553static int element_callback(snd_hctl_elem_t *elem, unsigned int mask)
1554{
1555	if (mask == SND_CTL_EVENT_MASK_REMOVE) {
1556		events_remove(elem);
1557		return 0;
1558	}
1559	if (mask & SND_CTL_EVENT_MASK_INFO)
1560		events_info(elem);
1561	if (mask & SND_CTL_EVENT_MASK_VALUE)
1562		events_value(elem);
1563	return 0;
1564}
1565
1566static void events_add(snd_hctl_elem_t *helem)
1567{
1568	snd_ctl_elem_id_t *id;
1569	snd_ctl_elem_id_alloca(&id);
1570	snd_hctl_elem_get_id(helem, id);
1571	printf("event add: ");
1572	show_control_id(id);
1573	printf("\n");
1574	snd_hctl_elem_set_callback(helem, element_callback);
1575}
1576
1577static int ctl_callback(snd_hctl_t *ctl ATTRIBUTE_UNUSED, unsigned int mask,
1578			snd_hctl_elem_t *elem)
1579{
1580	if (mask & SND_CTL_EVENT_MASK_ADD)
1581		events_add(elem);
1582	return 0;
1583}
1584
1585static int events(int argc ATTRIBUTE_UNUSED, char *argv[] ATTRIBUTE_UNUSED)
1586{
1587	snd_hctl_t *handle;
1588	snd_hctl_elem_t *helem;
1589	int err;
1590
1591	if ((err = snd_hctl_open(&handle, card, 0)) < 0) {
1592		error("Control %s open error: %s\n", card, snd_strerror(err));
1593		return err;
1594	}
1595	snd_hctl_set_callback(handle, ctl_callback);
1596	if ((err = snd_hctl_load(handle)) < 0) {
1597		error("Control %s hbuild error: %s\n", card, snd_strerror(err));
1598		return err;
1599	}
1600	for (helem = snd_hctl_first_elem(handle); helem; helem = snd_hctl_elem_next(helem)) {
1601		snd_hctl_elem_set_callback(helem, element_callback);
1602	}
1603	printf("Ready to listen...\n");
1604	while (1) {
1605		int res = snd_hctl_wait(handle, -1);
1606		if (res >= 0) {
1607			printf("Poll ok: %i\n", res);
1608			res = snd_hctl_handle_events(handle);
1609			if (res < 0)
1610				printf("ERR: %s (%d)\n", snd_strerror(res), res);
1611		}
1612	}
1613	snd_hctl_close(handle);
1614	return 0;
1615}
1616
1617static void sevents_value(snd_mixer_selem_id_t *sid)
1618{
1619	printf("event value: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
1620}
1621
1622static void sevents_info(snd_mixer_selem_id_t *sid)
1623{
1624	printf("event info: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
1625}
1626
1627static void sevents_remove(snd_mixer_selem_id_t *sid)
1628{
1629	printf("event remove: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
1630}
1631
1632static int melem_event(snd_mixer_elem_t *elem, unsigned int mask)
1633{
1634	snd_mixer_selem_id_t *sid;
1635	snd_mixer_selem_id_alloca(&sid);
1636	snd_mixer_selem_get_id(elem, sid);
1637	if (mask == SND_CTL_EVENT_MASK_REMOVE) {
1638		sevents_remove(sid);
1639		return 0;
1640	}
1641	if (mask & SND_CTL_EVENT_MASK_INFO)
1642		sevents_info(sid);
1643	if (mask & SND_CTL_EVENT_MASK_VALUE)
1644		sevents_value(sid);
1645	return 0;
1646}
1647
1648static void sevents_add(snd_mixer_elem_t *elem)
1649{
1650	snd_mixer_selem_id_t *sid;
1651	snd_mixer_selem_id_alloca(&sid);
1652	snd_mixer_selem_get_id(elem, sid);
1653	printf("event add: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
1654	snd_mixer_elem_set_callback(elem, melem_event);
1655}
1656
1657static int mixer_event(snd_mixer_t *mixer ATTRIBUTE_UNUSED, unsigned int mask,
1658		       snd_mixer_elem_t *elem)
1659{
1660	if (mask & SND_CTL_EVENT_MASK_ADD)
1661		sevents_add(elem);
1662	return 0;
1663}
1664
1665static int sevents(int argc ATTRIBUTE_UNUSED, char *argv[] ATTRIBUTE_UNUSED)
1666{
1667	snd_mixer_t *handle;
1668	int err;
1669
1670	if ((err = snd_mixer_open(&handle, 0)) < 0) {
1671		error("Mixer %s open error: %s", card, snd_strerror(err));
1672		return err;
1673	}
1674	if (smixer_level == 0 && (err = snd_mixer_attach(handle, card)) < 0) {
1675		error("Mixer attach %s error: %s", card, snd_strerror(err));
1676		snd_mixer_close(handle);
1677		return err;
1678	}
1679	if ((err = snd_mixer_selem_register(handle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) {
1680		error("Mixer register error: %s", snd_strerror(err));
1681		snd_mixer_close(handle);
1682		return err;
1683	}
1684	snd_mixer_set_callback(handle, mixer_event);
1685	err = snd_mixer_load(handle);
1686	if (err < 0) {
1687		error("Mixer %s load error: %s", card, snd_strerror(err));
1688		snd_mixer_close(handle);
1689		return err;
1690	}
1691
1692	printf("Ready to listen...\n");
1693	while (1) {
1694		int res;
1695		res = snd_mixer_wait(handle, -1);
1696		if (res >= 0) {
1697			printf("Poll ok: %i\n", res);
1698			res = snd_mixer_handle_events(handle);
1699			assert(res >= 0);
1700		}
1701	}
1702	snd_mixer_close(handle);
1703	return 0;
1704}
1705
1706/*
1707 * split a line into tokens
1708 * the content in the line buffer is modified
1709 */
1710static int split_line(char *buf, char **token, int max_token)
1711{
1712	char *dst;
1713	int n, esc, quote;
1714
1715	for (n = 0; n < max_token; n++) {
1716		while (isspace(*buf))
1717			buf++;
1718		if (! *buf || *buf == '\n')
1719			return n;
1720		/* skip comments */
1721		if (*buf == '#' || *buf == '!')
1722			return n;
1723		esc = 0;
1724		quote = 0;
1725		token[n] = buf;
1726		for (dst = buf; *buf && *buf != '\n'; buf++) {
1727			if (esc)
1728				esc = 0;
1729			else if (isspace(*buf) && !quote) {
1730				buf++;
1731				break;
1732			} else if (*buf == '\\') {
1733				esc = 1;
1734				continue;
1735			} else if (*buf == '\'' || *buf == '"') {
1736				if (! quote) {
1737					quote = *buf;
1738					continue;
1739				} else if (*buf == quote) {
1740					quote = 0;
1741					continue;
1742				}
1743			}
1744			*dst++ = *buf;
1745		}
1746		*dst = 0;
1747	}
1748	return n;
1749}
1750
1751#define MAX_ARGS	32
1752
1753static int exec_stdin(void)
1754{
1755	int narg;
1756	char buf[256], *args[MAX_ARGS];
1757	int err = 0;
1758
1759	/* quiet = 1; */
1760	ignore_error = 1;
1761
1762	while (fgets(buf, sizeof(buf), stdin)) {
1763		narg = split_line(buf, args, MAX_ARGS);
1764		if (narg > 0) {
1765			if (!strcmp(args[0], "sset") || !strcmp(args[0], "set"))
1766				err = sset(narg - 1, args + 1, 0, 1);
1767			else if (!strcmp(args[0], "cset"))
1768				err = cset(narg - 1, args + 1, 0, 1);
1769			if (err < 0)
1770				return 1;
1771		}
1772	}
1773	return 0;
1774}
1775
1776
1777int main(int argc, char *argv[])
1778{
1779	int badopt, retval, level = 0;
1780	int read_stdin = 0;
1781	static const struct option long_option[] =
1782	{
1783		{"help", 0, NULL, 'h'},
1784		{"card", 1, NULL, 'c'},
1785		{"device", 1, NULL, 'D'},
1786		{"quiet", 0, NULL, 'q'},
1787		{"inactive", 0, NULL, 'i'},
1788		{"debug", 0, NULL, 'd'},
1789		{"nocheck", 0, NULL, 'n'},
1790		{"version", 0, NULL, 'v'},
1791		{"abstract", 1, NULL, 'a'},
1792		{"stdin", 0, NULL, 's'},
1793		{"raw-volume", 0, NULL, 'R'},
1794		{"mapped-volume", 0, NULL, 'M'},
1795		{NULL, 0, NULL, 0},
1796	};
1797
1798	badopt = 0;
1799	while (1) {
1800		int c;
1801
1802		if ((c = getopt_long(argc, argv, "hc:D:qidnva:sRM", long_option, NULL)) < 0)
1803			break;
1804		switch (c) {
1805		case 'h':
1806			help();
1807			return 0;
1808		case 'c':
1809			{
1810				int i;
1811				i = snd_card_get_index(optarg);
1812				if (i >= 0 && i < 32)
1813#if defined(SND_LIB_VER) && SND_LIB_VER(1, 2, 5) <= SND_LIB_VERSION
1814					sprintf(card, "sysdefault:%i", i);
1815#else
1816					sprintf(card, "hw:%i", i);
1817#endif
1818				else {
1819					fprintf(stderr, "Invalid card number '%s'.\n", optarg);
1820					badopt++;
1821				}
1822			}
1823			break;
1824		case 'D':
1825			strncpy(card, optarg, sizeof(card)-1);
1826			card[sizeof(card)-1] = '\0';
1827			break;
1828		case 'q':
1829			quiet = 1;
1830			break;
1831		case 'i':
1832			level |= LEVEL_INACTIVE;
1833			break;
1834		case 'd':
1835			debugflag = 1;
1836			break;
1837		case 'n':
1838			no_check = 1;
1839			break;
1840		case 'v':
1841			printf("amixer version " SND_UTIL_VERSION_STR "\n");
1842			return 0;
1843		case 'a':
1844			smixer_level = 1;
1845			memset(&smixer_options, 0, sizeof(smixer_options));
1846			smixer_options.ver = 1;
1847			if (!strcmp(optarg, "none"))
1848				smixer_options.abstract = SND_MIXER_SABSTRACT_NONE;
1849			else if (!strcmp(optarg, "basic"))
1850				smixer_options.abstract = SND_MIXER_SABSTRACT_BASIC;
1851			else {
1852				fprintf(stderr, "Select correct abstraction level (none or basic)...\n");
1853				badopt++;
1854			}
1855			break;
1856		case 's':
1857			read_stdin = 1;
1858			break;
1859		case 'R':
1860			std_vol_type = VOL_RAW;
1861			break;
1862		case 'M':
1863			std_vol_type = VOL_MAP;
1864			break;
1865		default:
1866			fprintf(stderr, "Invalid switch or option -%c needs an argument.\n", c);
1867			badopt++;
1868		}
1869	}
1870	if (badopt)
1871		return 1;
1872
1873	smixer_options.device = card;
1874
1875	if (read_stdin) {
1876		retval = exec_stdin();
1877		goto finish;
1878	}
1879
1880	if (argc - optind <= 0) {
1881		retval = selems(LEVEL_BASIC | level) ? 1 : 0;
1882		goto finish;
1883	}
1884	if (!strcmp(argv[optind], "help")) {
1885		retval = help() ? 1 : 0;
1886	} else if (!strcmp(argv[optind], "info")) {
1887		retval = info() ? 1 : 0;
1888	} else if (!strcmp(argv[optind], "controls")) {
1889		retval = controls(level) ? 1 : 0;
1890	} else if (!strcmp(argv[optind], "contents")) {
1891		retval = controls(LEVEL_BASIC | level) ? 1 : 0;
1892	} else if (!strcmp(argv[optind], "scontrols") || !strcmp(argv[optind], "simple")) {
1893		retval = selems(level) ? 1 : 0;
1894	} else if (!strcmp(argv[optind], "scontents")) {
1895		retval = selems(LEVEL_BASIC | level) ? 1 : 0;
1896	} else if (!strcmp(argv[optind], "sset") || !strcmp(argv[optind], "set")) {
1897		retval = sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0, 0) ? 1 : 0;
1898	} else if (!strcmp(argv[optind], "sget") || !strcmp(argv[optind], "get")) {
1899		retval = sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1, 0) ? 1 : 0;
1900	} else if (!strcmp(argv[optind], "cset")) {
1901		retval = cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0, 0) ? 1 : 0;
1902	} else if (!strcmp(argv[optind], "cget")) {
1903		retval = cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1, 0) ? 1 : 0;
1904	} else if (!strcmp(argv[optind], "events")) {
1905		retval = events(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL);
1906	} else if (!strcmp(argv[optind], "sevents")) {
1907		retval = sevents(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL);
1908	} else {
1909		fprintf(stderr, "amixer: Unknown command '%s'...\n", argv[optind]);
1910		retval = 0;
1911	}
1912
1913finish:
1914	snd_config_update_free_global();
1915
1916	return retval;
1917}
1918