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 <unistd.h>
29 #include <string.h>
30 #include <ctype.h>
31 #include <math.h>
32 #include "control_local.h"
33 
34 /* Function to convert from percentage to volume. val = percentage */
35 
36 #ifdef HAVE_SOFT_FLOAT
convert_prange1(long val,long min,long max)37 static inline long int convert_prange1(long val, long min, long max)
38 {
39 	long temp = val * (max - min);
40 	return temp / 100 + min + ((temp % 100) == 0 ? 0 : 1);
41 }
42 #else
43 
44 #define convert_prange1(val, min, max) \
45 	ceil((val) * ((max) - (min)) * 0.01 + (min))
46 #endif
47 
48 #define check_range(val, min, max) \
49 	((val < min) ? (min) : ((val > max) ? (max) : (val)))
50 
get_integer(const char ** ptr,long min,long max)51 static long get_integer(const char **ptr, long min, long max)
52 {
53 	long val = min;
54 	char *p = (char *)*ptr, *s;
55 
56 	if (*p == ':')
57 		p++;
58 	if (*p == '\0' || (!isdigit(*p) && *p != '-'))
59 		goto out;
60 
61 	s = p;
62 	val = strtol(s, &p, 0);
63 	if (*p == '.') {
64 		p++;
65 		(void)strtol(p, &p, 10);
66 	}
67 	if (*p == '%') {
68 		val = (long)convert_prange1(strtod(s, NULL), min, max);
69 		p++;
70 	}
71 	val = check_range(val, min, max);
72 	if (*p == ',')
73 		p++;
74  out:
75 	*ptr = p;
76 	return val;
77 }
78 
get_integer64(const char ** ptr,long long min,long long max)79 static long long get_integer64(const char **ptr, long long min, long long max)
80 {
81 	long long val = min;
82 	char *p = (char *)*ptr, *s;
83 
84 	if (*p == ':')
85 		p++;
86 	if (*p == '\0' || (!isdigit(*p) && *p != '-'))
87 		goto out;
88 
89 	s = p;
90 	val = strtol(s, &p, 0);
91 	if (*p == '.') {
92 		p++;
93 		(void)strtol(p, &p, 10);
94 	}
95 	if (*p == '%') {
96 		val = (long long)convert_prange1(strtod(s, NULL), min, max);
97 		p++;
98 	}
99 	val = check_range(val, min, max);
100 	if (*p == ',')
101 		p++;
102  out:
103 	*ptr = p;
104 	return val;
105 }
106 
107 /**
108  * \brief return ASCII CTL element identifier name
109  * \param id CTL identifier
110  * \return ascii identifier of CTL element
111  *
112  * The string is allocated using strdup().
113  */
snd_ctl_ascii_elem_id_get(snd_ctl_elem_id_t * id)114 char *snd_ctl_ascii_elem_id_get(snd_ctl_elem_id_t *id)
115 {
116 	unsigned int index, device, subdevice;
117 	char buf[256], buf1[32];
118 
119 	snprintf(buf, sizeof(buf), "numid=%u,iface=%s,name='%s'",
120 				snd_ctl_elem_id_get_numid(id),
121 				snd_ctl_elem_iface_name(
122 					snd_ctl_elem_id_get_interface(id)),
123 				snd_ctl_elem_id_get_name(id));
124 	buf[sizeof(buf)-1] = '\0';
125 	index = snd_ctl_elem_id_get_index(id);
126 	device = snd_ctl_elem_id_get_device(id);
127 	subdevice = snd_ctl_elem_id_get_subdevice(id);
128 	if (index) {
129 		snprintf(buf1, sizeof(buf1), ",index=%u", index);
130 		if (strlen(buf) + strlen(buf1) < sizeof(buf))
131 			strcat(buf, buf1);
132 	}
133 	if (device) {
134 		snprintf(buf1, sizeof(buf1), ",device=%u", device);
135 		if (strlen(buf) + strlen(buf1) < sizeof(buf))
136 			strcat(buf, buf1);
137 	}
138 	if (subdevice) {
139 		snprintf(buf1, sizeof(buf1), ",subdevice=%u", subdevice);
140 		if (strlen(buf) + strlen(buf1) < sizeof(buf))
141 			strcat(buf, buf1);
142 	}
143 	return strdup(buf);
144 }
145 
146 #ifndef DOC_HIDDEN
147 /* used by UCM parser, too */
__snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t * dst,const char * str,const char ** ret_ptr)148 int __snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str,
149 				  const char **ret_ptr)
150 {
151 	int c, size, numid;
152 	int err = -EINVAL;
153 	char *ptr;
154 
155 	while (isspace(*str))
156 		str++;
157 	if (!(*str))
158 		goto out;
159 	snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER);	/* default */
160 	while (*str) {
161 		if (!strncasecmp(str, "numid=", 6)) {
162 			str += 6;
163 			numid = atoi(str);
164 			if (numid <= 0) {
165 				fprintf(stderr, "amixer: Invalid numid %d\n", numid);
166 				goto out;
167 			}
168 			snd_ctl_elem_id_set_numid(dst, atoi(str));
169 			while (isdigit(*str))
170 				str++;
171 		} else if (!strncasecmp(str, "iface=", 6)) {
172 			str += 6;
173 			if (!strncasecmp(str, "card", 4)) {
174 				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_CARD);
175 				str += 4;
176 			} else if (!strncasecmp(str, "mixer", 5)) {
177 				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER);
178 				str += 5;
179 			} else if (!strncasecmp(str, "pcm", 3)) {
180 				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_PCM);
181 				str += 3;
182 			} else if (!strncasecmp(str, "rawmidi", 7)) {
183 				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_RAWMIDI);
184 				str += 7;
185 			} else if (!strncasecmp(str, "timer", 5)) {
186 				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_TIMER);
187 				str += 5;
188 			} else if (!strncasecmp(str, "sequencer", 9)) {
189 				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_SEQUENCER);
190 				str += 9;
191 			} else {
192 				goto out;
193 			}
194 		} else if (!strncasecmp(str, "name=", 5)) {
195 			char buf[64];
196 			str += 5;
197 			ptr = buf;
198 			size = 0;
199 			if (*str == '\'' || *str == '\"') {
200 				c = *str++;
201 				while (*str && *str != c) {
202 					if (size < (int)sizeof(buf)) {
203 						*ptr++ = *str;
204 						size++;
205 					}
206 					str++;
207 				}
208 				if (*str == c)
209 					str++;
210 			} else {
211 				while (*str && *str != ',') {
212 					if (size < (int)sizeof(buf)) {
213 						*ptr++ = *str;
214 						size++;
215 					}
216 					str++;
217 				}
218 			}
219 			*ptr = '\0';
220 			snd_ctl_elem_id_set_name(dst, buf);
221 		} else if (!strncasecmp(str, "index=", 6)) {
222 			str += 6;
223 			snd_ctl_elem_id_set_index(dst, atoi(str));
224 			while (isdigit(*str))
225 				str++;
226 		} else if (!strncasecmp(str, "device=", 7)) {
227 			str += 7;
228 			snd_ctl_elem_id_set_device(dst, atoi(str));
229 			while (isdigit(*str))
230 				str++;
231 		} else if (!strncasecmp(str, "subdevice=", 10)) {
232 			str += 10;
233 			snd_ctl_elem_id_set_subdevice(dst, atoi(str));
234 			while (isdigit(*str))
235 				str++;
236 		}
237 		if (*str == ',') {
238 			str++;
239 		} else {
240 			/* when ret_ptr is given, allow to terminate gracefully
241 			 * at the next space letter
242 			 */
243 			if (ret_ptr && isspace(*str))
244 				break;
245 			if (*str)
246 				goto out;
247 		}
248 	}
249 	err = 0;
250 
251  out:
252 	if (ret_ptr)
253 		*ret_ptr = str;
254 	return err;
255 }
256 #endif
257 
258 /**
259  * \brief parse ASCII string as CTL element identifier
260  * \param dst destination CTL identifier
261  * \param str source ASCII string
262  * \return zero on success, otherwise a negative error code
263  */
snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t * dst,const char * str)264 int snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str)
265 {
266 	return __snd_ctl_ascii_elem_id_parse(dst, str, NULL);
267 }
268 
get_ctl_enum_item_index(snd_ctl_t * handle,snd_ctl_elem_info_t * info,const char ** ptrp)269 static int get_ctl_enum_item_index(snd_ctl_t *handle,
270 				   snd_ctl_elem_info_t *info,
271 				   const char **ptrp)
272 {
273 	char *ptr = (char *)*ptrp;
274 	int items, i, len;
275 	const char *name;
276 	char end;
277 
278 	items = snd_ctl_elem_info_get_items(info);
279 	if (items <= 0)
280 		return -1;
281 
282 	for (i = 0; i < items; i++) {
283 		snd_ctl_elem_info_set_item(info, i);
284 		if (snd_ctl_elem_info(handle, info) < 0)
285 			return -1;
286 		name = snd_ctl_elem_info_get_item_name(info);
287 		end = *ptr;
288 		if (end == '\'' || end == '"')
289 			ptr++;
290 		else
291 			end = '\0';
292 		len = strlen(name);
293 		if (strncmp(name, ptr, len) == 0) {
294 			if (ptr[len] == end || ptr[len] == ',' || ptr[len] == '\n') {
295 				ptr += len;
296 				*ptrp = ptr;
297 				return i;
298 			}
299 		}
300 	}
301 	return -1;
302 }
303 
304 /**
305  * \brief parse ASCII string as CTL element value
306  * \param handle CTL handle
307  * \param dst destination CTL element value
308  * \param info CTL element info structure
309  * \param value source ASCII string
310  * \return zero on success, otherwise a negative error code
311  *
312  * Note: For toggle command, the dst must contain previous (current)
313  * state (do the #snd_ctl_elem_read call to obtain it).
314  */
snd_ctl_ascii_value_parse(snd_ctl_t * handle,snd_ctl_elem_value_t * dst,snd_ctl_elem_info_t * info,const char * value)315 int snd_ctl_ascii_value_parse(snd_ctl_t *handle,
316 			      snd_ctl_elem_value_t *dst,
317 			      snd_ctl_elem_info_t *info,
318 			      const char *value)
319 {
320 	const char *ptr = value;
321 	snd_ctl_elem_id_t myid = {0};
322 	snd_ctl_elem_type_t type;
323 	unsigned int idx, count;
324 	long tmp;
325 	long long tmp64;
326 
327 	snd_ctl_elem_info_get_id(info, &myid);
328 	type = snd_ctl_elem_info_get_type(info);
329 	count = snd_ctl_elem_info_get_count(info);
330 	snd_ctl_elem_value_set_id(dst, &myid);
331 
332 	for (idx = 0; idx < count && idx < 128 && ptr && *ptr; idx++) {
333 		if (*ptr == ',')
334 			goto skip;
335 		switch (type) {
336 		case SND_CTL_ELEM_TYPE_BOOLEAN:
337 			tmp = 0;
338 			if (!strncasecmp(ptr, "on", 2) ||
339 			    !strncasecmp(ptr, "up", 2)) {
340 				tmp = 1;
341 				ptr += 2;
342 			} else if (!strncasecmp(ptr, "yes", 3)) {
343 				tmp = 1;
344 				ptr += 3;
345 			} else if (!strncasecmp(ptr, "toggle", 6)) {
346 				tmp = snd_ctl_elem_value_get_boolean(dst, idx);
347 				tmp = tmp > 0 ? 0 : 1;
348 				ptr += 6;
349 			} else if (isdigit(*ptr)) {
350 				tmp = atoi(ptr) > 0 ? 1 : 0;
351 				while (isdigit(*ptr))
352 					ptr++;
353 			} else {
354 				while (*ptr && *ptr != ',')
355 					ptr++;
356 			}
357 			snd_ctl_elem_value_set_boolean(dst, idx, tmp);
358 			break;
359 		case SND_CTL_ELEM_TYPE_INTEGER:
360 			tmp = get_integer(&ptr,
361 					  snd_ctl_elem_info_get_min(info),
362 					  snd_ctl_elem_info_get_max(info));
363 			snd_ctl_elem_value_set_integer(dst, idx, tmp);
364 			break;
365 		case SND_CTL_ELEM_TYPE_INTEGER64:
366 			tmp64 = get_integer64(&ptr,
367 					  snd_ctl_elem_info_get_min64(info),
368 					  snd_ctl_elem_info_get_max64(info));
369 			snd_ctl_elem_value_set_integer64(dst, idx, tmp64);
370 			break;
371 		case SND_CTL_ELEM_TYPE_ENUMERATED:
372 			tmp = get_ctl_enum_item_index(handle, info, &ptr);
373 			if (tmp < 0)
374 				tmp = get_integer(&ptr, 0,
375 					snd_ctl_elem_info_get_items(info) - 1);
376 			snd_ctl_elem_value_set_enumerated(dst, idx, tmp);
377 			break;
378 		case SND_CTL_ELEM_TYPE_BYTES:
379 			tmp = get_integer(&ptr, 0, 255);
380 			snd_ctl_elem_value_set_byte(dst, idx, tmp);
381 			break;
382 		default:
383 			break;
384 		}
385 	skip:
386 		if (!strchr(value, ','))
387 			ptr = value;
388 		else if (*ptr == ',')
389 			ptr++;
390 	}
391 	return 0;
392 }
393