xref: /third_party/alsa-lib/src/control/ctlparse.c (revision d5ac70f0)
1/**
2 * \file control/control.c
3 * \brief CTL interface - parse ASCII identifiers and values
4 * \author Jaroslav Kysela <perex@perex.cz>
5 * \date 2010
6 */
7/*
8 *  Control Interface - ASCII parser
9 *  Copyright (c) 2010 by Jaroslav Kysela <perex@perex.cz>
10 *
11 *
12 *   This library is free software; you can redistribute it and/or modify
13 *   it under the terms of the GNU Lesser General Public License as
14 *   published by the Free Software Foundation; either version 2.1 of
15 *   the License, or (at your option) any later version.
16 *
17 *   This program is distributed in the hope that it will be useful,
18 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
19 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 *   GNU Lesser General Public License for more details.
21 *
22 *   You should have received a copy of the GNU Lesser General Public
23 *   License along with this library; if not, write to the Free Software
24 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
25 *
26 */
27
28#include "control_local.h"
29#include <unistd.h>
30#include <string.h>
31#include <ctype.h>
32#include <math.h>
33
34/* Function to convert from percentage to volume. val = percentage */
35
36static inline long int convert_prange1(long perc, long min, long max)
37{
38	long tmp;
39
40#ifdef HAVE_SOFT_FLOAT
41	tmp = perc * (max - min);
42	tmp = tmp / 100 + ((tmp % 100) < 50 ? 0 : 1);
43#else
44	tmp = rint((double)perc * (double)(max - min) * 0.01);
45#endif
46	if (tmp == 0 && perc > 0)
47		tmp++;
48	return tmp + min;
49}
50
51#define check_range(val, min, max) \
52	((val < min) ? (min) : ((val > max) ? (max) : (val)))
53
54static long get_integer(const char **ptr, long min, long max)
55{
56	long val = min;
57	char *p = (char *)*ptr, *s;
58
59	if (*p == ':')
60		p++;
61	if (*p == '\0' || (!isdigit(*p) && *p != '-'))
62		goto out;
63
64	s = p;
65	val = strtol(s, &p, 0);
66	if (*p == '.') {
67		p++;
68		(void)strtol(p, &p, 10);
69	}
70	if (*p == '%') {
71		val = (long)convert_prange1(strtod(s, NULL), min, max);
72		p++;
73	}
74	val = check_range(val, min, max);
75	if (*p == ',')
76		p++;
77 out:
78	*ptr = p;
79	return val;
80}
81
82static long long get_integer64(const char **ptr, long long min, long long max)
83{
84	long long val = min;
85	char *p = (char *)*ptr, *s;
86
87	if (*p == ':')
88		p++;
89	if (*p == '\0' || (!isdigit(*p) && *p != '-'))
90		goto out;
91
92	s = p;
93	val = strtol(s, &p, 0);
94	if (*p == '.') {
95		p++;
96		(void)strtol(p, &p, 10);
97	}
98	if (*p == '%') {
99		val = (long long)convert_prange1(strtod(s, NULL), min, max);
100		p++;
101	}
102	val = check_range(val, min, max);
103	if (*p == ',')
104		p++;
105 out:
106	*ptr = p;
107	return val;
108}
109
110/**
111 * \brief return ASCII CTL element identifier name
112 * \param id CTL identifier
113 * \return ascii identifier of CTL element
114 *
115 * The string is allocated using strdup().
116 */
117char *snd_ctl_ascii_elem_id_get(snd_ctl_elem_id_t *id)
118{
119	unsigned int numid, index, device, subdevice;
120	char buf[256], buf1[32];
121	const char *iface;
122
123	numid = snd_ctl_elem_id_get_numid(id);
124	iface = snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(id));
125	if (numid > 0) {
126		snprintf(buf, sizeof(buf), "numid=%u,iface=%s,name='%s'",
127				numid, iface, snd_ctl_elem_id_get_name(id));
128	} else {
129		snprintf(buf, sizeof(buf), "iface=%s,name='%s'",
130				iface, snd_ctl_elem_id_get_name(id));
131	}
132	buf[sizeof(buf)-1] = '\0';
133	index = snd_ctl_elem_id_get_index(id);
134	device = snd_ctl_elem_id_get_device(id);
135	subdevice = snd_ctl_elem_id_get_subdevice(id);
136	if (index) {
137		snprintf(buf1, sizeof(buf1), ",index=%u", index);
138		if (strlen(buf) + strlen(buf1) < sizeof(buf))
139			strcat(buf, buf1);
140	}
141	if (device) {
142		snprintf(buf1, sizeof(buf1), ",device=%u", device);
143		if (strlen(buf) + strlen(buf1) < sizeof(buf))
144			strcat(buf, buf1);
145	}
146	if (subdevice) {
147		snprintf(buf1, sizeof(buf1), ",subdevice=%u", subdevice);
148		if (strlen(buf) + strlen(buf1) < sizeof(buf))
149			strcat(buf, buf1);
150	}
151	return strdup(buf);
152}
153
154#ifndef DOC_HIDDEN
155/* used by UCM parser, too */
156int __snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str,
157				  const char **ret_ptr)
158{
159	int c, size, numid;
160	int err = -EINVAL;
161	char *ptr;
162
163	while (isspace(*str))
164		str++;
165	if (!(*str))
166		goto out;
167	snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER);	/* default */
168	while (*str) {
169		if (!strncasecmp(str, "numid=", 6)) {
170			str += 6;
171			numid = atoi(str);
172			if (numid <= 0) {
173				fprintf(stderr, "amixer: Invalid numid %d\n", numid);
174				goto out;
175			}
176			snd_ctl_elem_id_set_numid(dst, atoi(str));
177			while (isdigit(*str))
178				str++;
179		} else if (!strncasecmp(str, "iface=", 6)) {
180			str += 6;
181			if (!strncasecmp(str, "card", 4)) {
182				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_CARD);
183				str += 4;
184			} else if (!strncasecmp(str, "mixer", 5)) {
185				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER);
186				str += 5;
187			} else if (!strncasecmp(str, "pcm", 3)) {
188				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_PCM);
189				str += 3;
190			} else if (!strncasecmp(str, "rawmidi", 7)) {
191				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_RAWMIDI);
192				str += 7;
193			} else if (!strncasecmp(str, "timer", 5)) {
194				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_TIMER);
195				str += 5;
196			} else if (!strncasecmp(str, "sequencer", 9)) {
197				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_SEQUENCER);
198				str += 9;
199			} else {
200				goto out;
201			}
202		} else if (!strncasecmp(str, "name=", 5)) {
203			char buf[64];
204			str += 5;
205			ptr = buf;
206			size = 0;
207			if (*str == '\'' || *str == '\"') {
208				c = *str++;
209				while (*str && *str != c) {
210					if (size < (int)sizeof(buf)) {
211						*ptr++ = *str;
212						size++;
213					}
214					str++;
215				}
216				if (*str == c)
217					str++;
218			} else {
219				while (*str && *str != ',') {
220					if (size < (int)sizeof(buf)) {
221						*ptr++ = *str;
222						size++;
223					}
224					str++;
225				}
226			}
227			*ptr = '\0';
228			snd_ctl_elem_id_set_name(dst, buf);
229		} else if (!strncasecmp(str, "index=", 6)) {
230			str += 6;
231			snd_ctl_elem_id_set_index(dst, atoi(str));
232			while (isdigit(*str))
233				str++;
234		} else if (!strncasecmp(str, "device=", 7)) {
235			str += 7;
236			snd_ctl_elem_id_set_device(dst, atoi(str));
237			while (isdigit(*str))
238				str++;
239		} else if (!strncasecmp(str, "subdevice=", 10)) {
240			str += 10;
241			snd_ctl_elem_id_set_subdevice(dst, atoi(str));
242			while (isdigit(*str))
243				str++;
244		}
245		if (*str == ',') {
246			str++;
247		} else {
248			/* when ret_ptr is given, allow to terminate gracefully
249			 * at the next space letter
250			 */
251			if (ret_ptr && isspace(*str))
252				break;
253			if (*str)
254				goto out;
255		}
256	}
257	err = 0;
258
259 out:
260	if (ret_ptr)
261		*ret_ptr = str;
262	return err;
263}
264#endif
265
266/**
267 * \brief parse ASCII string as CTL element identifier
268 * \param dst destination CTL identifier
269 * \param str source ASCII string
270 * \return zero on success, otherwise a negative error code
271 */
272int snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str)
273{
274	return __snd_ctl_ascii_elem_id_parse(dst, str, NULL);
275}
276
277static int get_ctl_enum_item_index(snd_ctl_t *handle,
278				   snd_ctl_elem_info_t *info,
279				   const char **ptrp)
280{
281	char *ptr = (char *)*ptrp;
282	int items, i, len;
283	const char *name;
284	char end;
285
286	items = snd_ctl_elem_info_get_items(info);
287	if (items <= 0)
288		return -1;
289
290	end = *ptr;
291	if (end == '\'' || end == '"')
292		ptr++;
293	else
294		end = '\0';
295
296	for (i = 0; i < items; i++) {
297		snd_ctl_elem_info_set_item(info, i);
298		if (snd_ctl_elem_info(handle, info) < 0)
299			return -1;
300		name = snd_ctl_elem_info_get_item_name(info);
301		len = strlen(name);
302		if (strncmp(name, ptr, len))
303			continue;
304		if (end == '\0' && (ptr[len] == '\0' || ptr[len] == ',' || ptr[len] == '\n')) {
305			*ptrp = ptr + len;
306			return i;
307		}
308		if (end != '\0' && ptr[len] == end) {
309			*ptrp = ptr + len + 1;
310			return i;
311		}
312	}
313	return -1;
314}
315
316static unsigned int get_ctl_type_max_elements(snd_ctl_elem_type_t type)
317{
318	struct snd_ctl_elem_value value;
319
320	switch (type) {
321	case SND_CTL_ELEM_TYPE_BOOLEAN:
322	case SND_CTL_ELEM_TYPE_INTEGER:
323		return ARRAY_SIZE(value.value.integer.value);
324	case SND_CTL_ELEM_TYPE_INTEGER64:
325		return ARRAY_SIZE(value.value.integer64.value);
326	case SND_CTL_ELEM_TYPE_ENUMERATED:
327		return ARRAY_SIZE(value.value.enumerated.item);
328	case SND_CTL_ELEM_TYPE_BYTES:
329		return ARRAY_SIZE(value.value.bytes.data);
330	default:
331		return 0;
332	}
333}
334
335/**
336 * \brief parse ASCII string as CTL element value
337 * \param handle CTL handle
338 * \param dst destination CTL element value
339 * \param info CTL element info structure
340 * \param value source ASCII string
341 * \return zero on success, otherwise a negative error code
342 *
343 * Note: For toggle command, the dst must contain previous (current)
344 * state (do the #snd_ctl_elem_read call to obtain it).
345 */
346int snd_ctl_ascii_value_parse(snd_ctl_t *handle,
347			      snd_ctl_elem_value_t *dst,
348			      snd_ctl_elem_info_t *info,
349			      const char *value)
350{
351	const char *ptr = value;
352	snd_ctl_elem_id_t myid = {0};
353	snd_ctl_elem_type_t type;
354	unsigned int idx, count;
355	long tmp;
356	long long tmp64;
357
358	snd_ctl_elem_info_get_id(info, &myid);
359	type = snd_ctl_elem_info_get_type(info);
360	count = snd_ctl_elem_info_get_count(info);
361	snd_ctl_elem_value_set_id(dst, &myid);
362
363	if (count > get_ctl_type_max_elements(type))
364		count = get_ctl_type_max_elements(type);
365
366	for (idx = 0; idx < count && ptr && *ptr; idx++) {
367		if (*ptr == ',')
368			goto skip;
369		switch (type) {
370		case SND_CTL_ELEM_TYPE_BOOLEAN:
371			tmp = 0;
372			if (!strncasecmp(ptr, "on", 2) ||
373			    !strncasecmp(ptr, "up", 2)) {
374				tmp = 1;
375				ptr += 2;
376			} else if (!strncasecmp(ptr, "yes", 3)) {
377				tmp = 1;
378				ptr += 3;
379			} else if (!strncasecmp(ptr, "toggle", 6)) {
380				tmp = snd_ctl_elem_value_get_boolean(dst, idx);
381				tmp = tmp > 0 ? 0 : 1;
382				ptr += 6;
383			} else if (isdigit(*ptr)) {
384				tmp = atoi(ptr) > 0 ? 1 : 0;
385				while (isdigit(*ptr))
386					ptr++;
387			} else {
388				while (*ptr && *ptr != ',')
389					ptr++;
390			}
391			snd_ctl_elem_value_set_boolean(dst, idx, tmp);
392			break;
393		case SND_CTL_ELEM_TYPE_INTEGER:
394			tmp = get_integer(&ptr,
395					  snd_ctl_elem_info_get_min(info),
396					  snd_ctl_elem_info_get_max(info));
397			snd_ctl_elem_value_set_integer(dst, idx, tmp);
398			break;
399		case SND_CTL_ELEM_TYPE_INTEGER64:
400			tmp64 = get_integer64(&ptr,
401					  snd_ctl_elem_info_get_min64(info),
402					  snd_ctl_elem_info_get_max64(info));
403			snd_ctl_elem_value_set_integer64(dst, idx, tmp64);
404			break;
405		case SND_CTL_ELEM_TYPE_ENUMERATED:
406			tmp = get_ctl_enum_item_index(handle, info, &ptr);
407			if (tmp < 0)
408				tmp = get_integer(&ptr, 0,
409					snd_ctl_elem_info_get_items(info) - 1);
410			snd_ctl_elem_value_set_enumerated(dst, idx, tmp);
411			break;
412		case SND_CTL_ELEM_TYPE_BYTES:
413			tmp = get_integer(&ptr, 0, 255);
414			snd_ctl_elem_value_set_byte(dst, idx, tmp);
415			break;
416		default:
417			break;
418		}
419	skip:
420		if (!strchr(value, ','))
421			ptr = value;
422		else if (*ptr == ',')
423			ptr++;
424	}
425	return 0;
426}
427