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