| /** | |
| * \file control/control.c | |
| * \brief CTL interface - parse ASCII identifiers and values | |
| * \author Jaroslav Kysela <perex@perex.cz> | |
| * \date 2010 | |
| */ | |
| /* | |
| * Control Interface - ASCII parser | |
| * Copyright (c) 2010 by Jaroslav Kysela <perex@perex.cz> | |
| * | |
| * | |
| * This library is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as | |
| * published by the Free Software Foundation; either version 2.1 of | |
| * the License, or (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| #include "control_local.h" | |
| #include <unistd.h> | |
| #include <string.h> | |
| #include <ctype.h> | |
| #include <math.h> | |
| /* Function to convert from percentage to volume. val = percentage */ | |
| static inline long int convert_prange1(long perc, long min, long max) | |
| { | |
| long tmp; | |
| #ifdef HAVE_SOFT_FLOAT | |
| tmp = perc * (max - min); | |
| tmp = tmp / 100 + ((tmp % 100) < 50 ? 0 : 1); | |
| #else | |
| tmp = rint((double)perc * (double)(max - min) * 0.01); | |
| #endif | |
| if (tmp == 0 && perc > 0) | |
| tmp++; | |
| return tmp + min; | |
| } | |
| #define check_range(val, min, max) \ | |
| ((val < min) ? (min) : ((val > max) ? (max) : (val))) | |
| static long get_integer(const char **ptr, long min, long max) | |
| { | |
| long val = min; | |
| char *p = (char *)*ptr, *s; | |
| if (*p == ':') | |
| p++; | |
| if (*p == '\0' || (!isdigit(*p) && *p != '-')) | |
| goto out; | |
| s = p; | |
| val = strtol(s, &p, 0); | |
| if (*p == '.') { | |
| p++; | |
| (void)strtol(p, &p, 10); | |
| } | |
| if (*p == '%') { | |
| val = (long)convert_prange1(strtod(s, NULL), min, max); | |
| p++; | |
| } | |
| val = check_range(val, min, max); | |
| if (*p == ',') | |
| p++; | |
| out: | |
| *ptr = p; | |
| return val; | |
| } | |
| static long long get_integer64(const char **ptr, long long min, long long max) | |
| { | |
| long long val = min; | |
| char *p = (char *)*ptr, *s; | |
| if (*p == ':') | |
| p++; | |
| if (*p == '\0' || (!isdigit(*p) && *p != '-')) | |
| goto out; | |
| s = p; | |
| val = strtol(s, &p, 0); | |
| if (*p == '.') { | |
| p++; | |
| (void)strtol(p, &p, 10); | |
| } | |
| if (*p == '%') { | |
| val = (long long)convert_prange1(strtod(s, NULL), min, max); | |
| p++; | |
| } | |
| val = check_range(val, min, max); | |
| if (*p == ',') | |
| p++; | |
| out: | |
| *ptr = p; | |
| return val; | |
| } | |
| /** | |
| * \brief return ASCII CTL element identifier name | |
| * \param id CTL identifier | |
| * \return ascii identifier of CTL element | |
| * | |
| * The string is allocated using strdup(). | |
| */ | |
| char *snd_ctl_ascii_elem_id_get(snd_ctl_elem_id_t *id) | |
| { | |
| unsigned int numid, index, device, subdevice; | |
| char buf[256], buf1[32]; | |
| const char *iface; | |
| numid = snd_ctl_elem_id_get_numid(id); | |
| iface = snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(id)); | |
| if (numid > 0) { | |
| snprintf(buf, sizeof(buf), "numid=%u,iface=%s,name='%s'", | |
| numid, iface, snd_ctl_elem_id_get_name(id)); | |
| } else { | |
| snprintf(buf, sizeof(buf), "iface=%s,name='%s'", | |
| iface, snd_ctl_elem_id_get_name(id)); | |
| } | |
| buf[sizeof(buf)-1] = '\0'; | |
| index = snd_ctl_elem_id_get_index(id); | |
| device = snd_ctl_elem_id_get_device(id); | |
| subdevice = snd_ctl_elem_id_get_subdevice(id); | |
| if (index) { | |
| snprintf(buf1, sizeof(buf1), ",index=%u", index); | |
| if (strlen(buf) + strlen(buf1) < sizeof(buf)) | |
| strcat(buf, buf1); | |
| } | |
| if (device) { | |
| snprintf(buf1, sizeof(buf1), ",device=%u", device); | |
| if (strlen(buf) + strlen(buf1) < sizeof(buf)) | |
| strcat(buf, buf1); | |
| } | |
| if (subdevice) { | |
| snprintf(buf1, sizeof(buf1), ",subdevice=%u", subdevice); | |
| if (strlen(buf) + strlen(buf1) < sizeof(buf)) | |
| strcat(buf, buf1); | |
| } | |
| return strdup(buf); | |
| } | |
| #ifndef DOC_HIDDEN | |
| /* used by UCM parser, too */ | |
| int __snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str, | |
| const char **ret_ptr) | |
| { | |
| int c, size, numid; | |
| int err = -EINVAL; | |
| char *ptr; | |
| while (isspace(*str)) | |
| str++; | |
| if (!(*str)) | |
| goto out; | |
| snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER); /* default */ | |
| while (*str) { | |
| if (!strncasecmp(str, "numid=", 6)) { | |
| str += 6; | |
| numid = atoi(str); | |
| if (numid <= 0) { | |
| fprintf(stderr, "amixer: Invalid numid %d\n", numid); | |
| goto out; | |
| } | |
| snd_ctl_elem_id_set_numid(dst, atoi(str)); | |
| while (isdigit(*str)) | |
| str++; | |
| } else if (!strncasecmp(str, "iface=", 6)) { | |
| str += 6; | |
| if (!strncasecmp(str, "card", 4)) { | |
| snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_CARD); | |
| str += 4; | |
| } else if (!strncasecmp(str, "mixer", 5)) { | |
| snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER); | |
| str += 5; | |
| } else if (!strncasecmp(str, "pcm", 3)) { | |
| snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_PCM); | |
| str += 3; | |
| } else if (!strncasecmp(str, "rawmidi", 7)) { | |
| snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_RAWMIDI); | |
| str += 7; | |
| } else if (!strncasecmp(str, "timer", 5)) { | |
| snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_TIMER); | |
| str += 5; | |
| } else if (!strncasecmp(str, "sequencer", 9)) { | |
| snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_SEQUENCER); | |
| str += 9; | |
| } else { | |
| goto out; | |
| } | |
| } else if (!strncasecmp(str, "name=", 5)) { | |
| char buf[64]; | |
| str += 5; | |
| ptr = buf; | |
| size = 0; | |
| if (*str == '\'' || *str == '\"') { | |
| c = *str++; | |
| while (*str && *str != c) { | |
| if (size < (int)sizeof(buf)) { | |
| *ptr++ = *str; | |
| size++; | |
| } | |
| str++; | |
| } | |
| if (*str == c) | |
| str++; | |
| } else { | |
| while (*str && *str != ',') { | |
| if (size < (int)sizeof(buf)) { | |
| *ptr++ = *str; | |
| size++; | |
| } | |
| str++; | |
| } | |
| } | |
| *ptr = '\0'; | |
| snd_ctl_elem_id_set_name(dst, buf); | |
| } else if (!strncasecmp(str, "index=", 6)) { | |
| str += 6; | |
| snd_ctl_elem_id_set_index(dst, atoi(str)); | |
| while (isdigit(*str)) | |
| str++; | |
| } else if (!strncasecmp(str, "device=", 7)) { | |
| str += 7; | |
| snd_ctl_elem_id_set_device(dst, atoi(str)); | |
| while (isdigit(*str)) | |
| str++; | |
| } else if (!strncasecmp(str, "subdevice=", 10)) { | |
| str += 10; | |
| snd_ctl_elem_id_set_subdevice(dst, atoi(str)); | |
| while (isdigit(*str)) | |
| str++; | |
| } | |
| if (*str == ',') { | |
| str++; | |
| } else { | |
| /* when ret_ptr is given, allow to terminate gracefully | |
| * at the next space letter | |
| */ | |
| if (ret_ptr && isspace(*str)) | |
| break; | |
| if (*str) | |
| goto out; | |
| } | |
| } | |
| err = 0; | |
| out: | |
| if (ret_ptr) | |
| *ret_ptr = str; | |
| return err; | |
| } | |
| #endif | |
| /** | |
| * \brief parse ASCII string as CTL element identifier | |
| * \param dst destination CTL identifier | |
| * \param str source ASCII string | |
| * \return zero on success, otherwise a negative error code | |
| */ | |
| int snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str) | |
| { | |
| return __snd_ctl_ascii_elem_id_parse(dst, str, NULL); | |
| } | |
| static int get_ctl_enum_item_index(snd_ctl_t *handle, | |
| snd_ctl_elem_info_t *info, | |
| const char **ptrp) | |
| { | |
| char *ptr = (char *)*ptrp; | |
| int items, i, len; | |
| const char *name; | |
| char end; | |
| items = snd_ctl_elem_info_get_items(info); | |
| if (items <= 0) | |
| return -1; | |
| end = *ptr; | |
| if (end == '\'' || end == '"') | |
| ptr++; | |
| else | |
| end = '\0'; | |
| for (i = 0; i < items; i++) { | |
| snd_ctl_elem_info_set_item(info, i); | |
| if (snd_ctl_elem_info(handle, info) < 0) | |
| return -1; | |
| name = snd_ctl_elem_info_get_item_name(info); | |
| len = strlen(name); | |
| if (strncmp(name, ptr, len)) | |
| continue; | |
| if (end == '\0' && (ptr[len] == '\0' || ptr[len] == ',' || ptr[len] == '\n')) { | |
| *ptrp = ptr + len; | |
| return i; | |
| } | |
| if (end != '\0' && ptr[len] == end) { | |
| *ptrp = ptr + len + 1; | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| static unsigned int get_ctl_type_max_elements(snd_ctl_elem_type_t type) | |
| { | |
| struct snd_ctl_elem_value value; | |
| switch (type) { | |
| case SND_CTL_ELEM_TYPE_BOOLEAN: | |
| case SND_CTL_ELEM_TYPE_INTEGER: | |
| return ARRAY_SIZE(value.value.integer.value); | |
| case SND_CTL_ELEM_TYPE_INTEGER64: | |
| return ARRAY_SIZE(value.value.integer64.value); | |
| case SND_CTL_ELEM_TYPE_ENUMERATED: | |
| return ARRAY_SIZE(value.value.enumerated.item); | |
| case SND_CTL_ELEM_TYPE_BYTES: | |
| return ARRAY_SIZE(value.value.bytes.data); | |
| default: | |
| return 0; | |
| } | |
| } | |
| /** | |
| * \brief parse ASCII string as CTL element value | |
| * \param handle CTL handle | |
| * \param dst destination CTL element value | |
| * \param info CTL element info structure | |
| * \param value source ASCII string | |
| * \return zero on success, otherwise a negative error code | |
| * | |
| * Note: For toggle command, the dst must contain previous (current) | |
| * state (do the #snd_ctl_elem_read call to obtain it). | |
| */ | |
| int snd_ctl_ascii_value_parse(snd_ctl_t *handle, | |
| snd_ctl_elem_value_t *dst, | |
| snd_ctl_elem_info_t *info, | |
| const char *value) | |
| { | |
| const char *ptr = value; | |
| snd_ctl_elem_id_t myid = {0}; | |
| snd_ctl_elem_type_t type; | |
| unsigned int idx, count; | |
| long tmp; | |
| long long tmp64; | |
| snd_ctl_elem_info_get_id(info, &myid); | |
| type = snd_ctl_elem_info_get_type(info); | |
| count = snd_ctl_elem_info_get_count(info); | |
| snd_ctl_elem_value_set_id(dst, &myid); | |
| if (count > get_ctl_type_max_elements(type)) | |
| count = get_ctl_type_max_elements(type); | |
| for (idx = 0; idx < count && ptr && *ptr; idx++) { | |
| if (*ptr == ',') | |
| goto skip; | |
| switch (type) { | |
| case SND_CTL_ELEM_TYPE_BOOLEAN: | |
| tmp = 0; | |
| if (!strncasecmp(ptr, "on", 2) || | |
| !strncasecmp(ptr, "up", 2)) { | |
| tmp = 1; | |
| ptr += 2; | |
| } else if (!strncasecmp(ptr, "yes", 3)) { | |
| tmp = 1; | |
| ptr += 3; | |
| } else if (!strncasecmp(ptr, "toggle", 6)) { | |
| tmp = snd_ctl_elem_value_get_boolean(dst, idx); | |
| tmp = tmp > 0 ? 0 : 1; | |
| ptr += 6; | |
| } else if (isdigit(*ptr)) { | |
| tmp = atoi(ptr) > 0 ? 1 : 0; | |
| while (isdigit(*ptr)) | |
| ptr++; | |
| } else { | |
| while (*ptr && *ptr != ',') | |
| ptr++; | |
| } | |
| snd_ctl_elem_value_set_boolean(dst, idx, tmp); | |
| break; | |
| case SND_CTL_ELEM_TYPE_INTEGER: | |
| tmp = get_integer(&ptr, | |
| snd_ctl_elem_info_get_min(info), | |
| snd_ctl_elem_info_get_max(info)); | |
| snd_ctl_elem_value_set_integer(dst, idx, tmp); | |
| break; | |
| case SND_CTL_ELEM_TYPE_INTEGER64: | |
| tmp64 = get_integer64(&ptr, | |
| snd_ctl_elem_info_get_min64(info), | |
| snd_ctl_elem_info_get_max64(info)); | |
| snd_ctl_elem_value_set_integer64(dst, idx, tmp64); | |
| break; | |
| case SND_CTL_ELEM_TYPE_ENUMERATED: | |
| tmp = get_ctl_enum_item_index(handle, info, &ptr); | |
| if (tmp < 0) | |
| tmp = get_integer(&ptr, 0, | |
| snd_ctl_elem_info_get_items(info) - 1); | |
| snd_ctl_elem_value_set_enumerated(dst, idx, tmp); | |
| break; | |
| case SND_CTL_ELEM_TYPE_BYTES: | |
| tmp = get_integer(&ptr, 0, 255); | |
| snd_ctl_elem_value_set_byte(dst, idx, tmp); | |
| break; | |
| default: | |
| break; | |
| } | |
| skip: | |
| if (!strchr(value, ',')) | |
| ptr = value; | |
| else if (*ptr == ',') | |
| ptr++; | |
| } | |
| return 0; | |
| } |