| /* |
| * Advanced Linux Sound Architecture Control Program |
| * Copyright (c) by Abramo Bagnara <abramo@alsa-project.org> |
| * Jaroslav Kysela <perex@perex.cz> |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 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 General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| #include "aconfig.h" |
| #include "version.h" |
| #include <getopt.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <alsa/asoundlib.h> |
| #include "alsactl.h" |
| |
| |
| static char *id_str(snd_ctl_elem_id_t *id) |
| { |
| static char str[128]; |
| assert(id); |
| sprintf(str, "%i,%i,%i,%s,%i", |
| snd_ctl_elem_id_get_interface(id), |
| snd_ctl_elem_id_get_device(id), |
| snd_ctl_elem_id_get_subdevice(id), |
| snd_ctl_elem_id_get_name(id), |
| snd_ctl_elem_id_get_index(id)); |
| return str; |
| } |
| |
| static char *num_str(long n) |
| { |
| static char str[32]; |
| sprintf(str, "%ld", n); |
| return str; |
| } |
| |
| static int snd_config_integer_add(snd_config_t *father, char *id, long integer) |
| { |
| int err; |
| snd_config_t *leaf; |
| err = snd_config_imake_integer(&leaf, id, integer); |
| if (err < 0) |
| return err; |
| err = snd_config_add(father, leaf); |
| if (err < 0) { |
| snd_config_delete(leaf); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int snd_config_integer64_add(snd_config_t *father, char *id, long long integer) |
| { |
| int err; |
| snd_config_t *leaf; |
| err = snd_config_imake_integer64(&leaf, id, integer); |
| if (err < 0) |
| return err; |
| err = snd_config_add(father, leaf); |
| if (err < 0) { |
| snd_config_delete(leaf); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int snd_config_string_add(snd_config_t *father, const char *id, const char *string) |
| { |
| int err; |
| snd_config_t *leaf; |
| err = snd_config_imake_string(&leaf, id, string); |
| if (err < 0) |
| return err; |
| err = snd_config_add(father, leaf); |
| if (err < 0) { |
| snd_config_delete(leaf); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int snd_config_compound_add(snd_config_t *father, const char *id, int join, |
| snd_config_t **node) |
| { |
| int err; |
| snd_config_t *leaf; |
| err = snd_config_make_compound(&leaf, id, join); |
| if (err < 0) |
| return err; |
| err = snd_config_add(father, leaf); |
| if (err < 0) { |
| snd_config_delete(leaf); |
| return err; |
| } |
| *node = leaf; |
| return 0; |
| } |
| |
| #define MAX_USER_TLV_SIZE 64 |
| |
| static char *tlv_to_str(unsigned int *tlv) |
| { |
| int i, len = tlv[1] / 4 + 2; |
| char *s, *p; |
| |
| if (len >= MAX_USER_TLV_SIZE) |
| return NULL; |
| s = malloc(len * 8 + 1); |
| if (! s) |
| return NULL; |
| p = s; |
| for (i = 0; i < len; i++) { |
| sprintf(p, "%08x", tlv[i]); |
| p += 8; |
| } |
| return s; |
| } |
| |
| static unsigned int *str_to_tlv(const char *s) |
| { |
| int i, j, c, len; |
| unsigned int *tlv; |
| |
| len = strlen(s); |
| if (len % 8) /* aligned to 4 bytes (= 8 letters) */ |
| return NULL; |
| len /= 8; |
| if (len > MAX_USER_TLV_SIZE) |
| return NULL; |
| tlv = malloc(sizeof(int) * len); |
| if (! tlv) |
| return NULL; |
| for (i = 0; i < len; i++) { |
| tlv[i] = 0; |
| for (j = 0; j < 8; j++) { |
| if ((c = hextodigit(*s++)) < 0) { |
| free(tlv); |
| return NULL; |
| } |
| tlv[i] = (tlv[i] << 4) | c; |
| } |
| } |
| return tlv; |
| } |
| |
| /* |
| * add the TLV string, dB ranges, and dB values to comment fields |
| */ |
| static int add_tlv_comments(snd_ctl_t *handle, snd_ctl_elem_id_t *id, |
| snd_ctl_elem_info_t *info, snd_ctl_elem_value_t *ctl, |
| snd_config_t *comment) |
| { |
| unsigned int tlv[MAX_USER_TLV_SIZE]; |
| unsigned int *db; |
| long rangemin, rangemax; |
| long dbmin, dbmax, dbgain; |
| snd_config_t *value; |
| unsigned int i, count; |
| int err; |
| |
| if (snd_ctl_elem_tlv_read(handle, id, tlv, sizeof(tlv)) < 0) |
| return 0; /* ignore error */ |
| |
| if (snd_ctl_elem_info_is_tlv_writable(info)) { |
| char *s = tlv_to_str(tlv); |
| if (s) { |
| err = snd_config_string_add(comment, "tlv", s); |
| free(s); |
| if (err < 0) { |
| error("snd_config_string_add: %s", snd_strerror(err)); |
| return err; |
| } |
| } |
| } |
| |
| err = snd_tlv_parse_dB_info(tlv, sizeof(tlv), &db); |
| if (err <= 0) |
| return 0; |
| |
| rangemin = snd_ctl_elem_info_get_min(info); |
| rangemax = snd_ctl_elem_info_get_max(info); |
| snd_tlv_get_dB_range(db, rangemin, rangemax, &dbmin, &dbmax); |
| if (err < 0) |
| return err; |
| snd_config_integer_add(comment, "dbmin", dbmin); |
| snd_config_integer_add(comment, "dbmax", dbmax); |
| |
| if (snd_ctl_elem_info_get_type(info) == SND_CTL_ELEM_TYPE_INTEGER) { |
| err = snd_config_compound_add(comment, "dbvalue", 1, &value); |
| if (err < 0) { |
| error("snd_config_compound_add: %s", snd_strerror(err)); |
| return err; |
| } |
| count = snd_ctl_elem_info_get_count(info); |
| for (i = 0; i < count; i++) { |
| err = snd_tlv_convert_to_dB(db, rangemin, rangemax, |
| snd_ctl_elem_value_get_integer(ctl, i), &dbgain); |
| if (err < 0) { |
| error("snd_tlv_convert_to_dB: %s", snd_strerror(err)); |
| return err; |
| } |
| err = snd_config_integer_add(value, num_str(i), dbgain); |
| if (err < 0) { |
| error("snd_config_integer_add: %s", snd_strerror(err)); |
| return err; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static int get_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, snd_config_t *top) |
| { |
| snd_ctl_elem_value_t *ctl; |
| snd_ctl_elem_info_t *info; |
| snd_config_t *control, *comment, *item, *value; |
| const char *s; |
| char buf[256]; |
| unsigned int idx; |
| int err; |
| unsigned int device, subdevice, index; |
| const char *name; |
| snd_ctl_elem_type_t type; |
| unsigned int count; |
| snd_ctl_elem_value_alloca(&ctl); |
| snd_ctl_elem_info_alloca(&info); |
| snd_ctl_elem_info_set_id(info, id); |
| err = snd_ctl_elem_info(handle, info); |
| if (err < 0) { |
| error("Cannot read control info '%s': %s", id_str(id), snd_strerror(err)); |
| return err; |
| } |
| |
| if (!snd_ctl_elem_info_is_readable(info)) |
| return 0; |
| snd_ctl_elem_value_set_id(ctl, id); |
| err = snd_ctl_elem_read(handle, ctl); |
| if (err < 0) { |
| error("Cannot read control '%s': %s", id_str(id), snd_strerror(err)); |
| return err; |
| } |
| |
| err = snd_config_compound_add(top, num_str(snd_ctl_elem_info_get_numid(info)), 0, &control); |
| if (err < 0) { |
| error("snd_config_compound_add: %s", snd_strerror(err)); |
| return err; |
| } |
| err = snd_config_make_compound(&comment, "comment", 0); |
| if (err < 0) { |
| error("snd_config_make_compound: %s", snd_strerror(err)); |
| return err; |
| } |
| |
| buf[0] = '\0'; |
| buf[1] = '\0'; |
| if (snd_ctl_elem_info_is_readable(info)) |
| strcat(buf, " read"); |
| if (snd_ctl_elem_info_is_writable(info)) |
| strcat(buf, " write"); |
| if (snd_ctl_elem_info_is_inactive(info)) |
| strcat(buf, " inactive"); |
| if (snd_ctl_elem_info_is_volatile(info)) |
| strcat(buf, " volatile"); |
| if (snd_ctl_elem_info_is_locked(info)) |
| strcat(buf, " locked"); |
| if (snd_ctl_elem_info_is_user(info)) |
| strcat(buf, " user"); |
| err = snd_config_string_add(comment, "access", buf + 1); |
| if (err < 0) { |
| error("snd_config_string_add: %s", snd_strerror(err)); |
| return err; |
| } |
| |
| type = snd_ctl_elem_info_get_type(info); |
| device = snd_ctl_elem_info_get_device(info); |
| subdevice = snd_ctl_elem_info_get_subdevice(info); |
| index = snd_ctl_elem_info_get_index(info); |
| name = snd_ctl_elem_info_get_name(info); |
| count = snd_ctl_elem_info_get_count(info); |
| s = snd_ctl_elem_type_name(type); |
| err = snd_config_string_add(comment, "type", s); |
| if (err < 0) { |
| error("snd_config_string_add: %s", snd_strerror(err)); |
| return err; |
| } |
| err = snd_config_integer_add(comment, "count", count); |
| if (err < 0) { |
| error("snd_config_integer_add: %s", snd_strerror(err)); |
| return err; |
| } |
| |
| switch (type) { |
| case SND_CTL_ELEM_TYPE_BOOLEAN: |
| break; |
| case SND_CTL_ELEM_TYPE_INTEGER: |
| { |
| long min = snd_ctl_elem_info_get_min(info); |
| long max = snd_ctl_elem_info_get_max(info); |
| long step = snd_ctl_elem_info_get_step(info); |
| if (step) |
| sprintf(buf, "%li - %li (step %li)", min, max, step); |
| else |
| sprintf(buf, "%li - %li", min, max); |
| err = snd_config_string_add(comment, "range", buf); |
| if (err < 0) { |
| error("snd_config_string_add: %s", snd_strerror(err)); |
| return err; |
| } |
| if (snd_ctl_elem_info_is_tlv_readable(info)) { |
| err = add_tlv_comments(handle, id, info, ctl, comment); |
| if (err < 0) |
| return err; |
| } |
| break; |
| } |
| case SND_CTL_ELEM_TYPE_INTEGER64: |
| { |
| long long min = snd_ctl_elem_info_get_min64(info); |
| long long max = snd_ctl_elem_info_get_max64(info); |
| long long step = snd_ctl_elem_info_get_step64(info); |
| if (step) |
| sprintf(buf, "%Li - %Li (step %Li)", min, max, step); |
| else |
| sprintf(buf, "%Li - %Li", min, max); |
| err = snd_config_string_add(comment, "range", buf); |
| if (err < 0) { |
| error("snd_config_string_add: %s", snd_strerror(err)); |
| return err; |
| } |
| break; |
| } |
| case SND_CTL_ELEM_TYPE_ENUMERATED: |
| { |
| unsigned int items; |
| err = snd_config_compound_add(comment, "item", 1, &item); |
| if (err < 0) { |
| error("snd_config_compound_add: %s", snd_strerror(err)); |
| return err; |
| } |
| items = snd_ctl_elem_info_get_items(info); |
| for (idx = 0; idx < items; idx++) { |
| snd_ctl_elem_info_set_item(info, idx); |
| err = snd_ctl_elem_info(handle, info); |
| if (err < 0) { |
| error("snd_ctl_card_info: %s", snd_strerror(err)); |
| return err; |
| } |
| err = snd_config_string_add(item, num_str(idx), snd_ctl_elem_info_get_item_name(info)); |
| if (err < 0) { |
| error("snd_config_string_add: %s", snd_strerror(err)); |
| return err; |
| } |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| s = snd_ctl_elem_iface_name(snd_ctl_elem_info_get_interface(info)); |
| err = snd_config_string_add(control, "iface", s); |
| if (err < 0) { |
| error("snd_config_string_add: %s", snd_strerror(err)); |
| return err; |
| } |
| if (device != 0) { |
| err = snd_config_integer_add(control, "device", device); |
| if (err < 0) { |
| error("snd_config_integer_add: %s", snd_strerror(err)); |
| return err; |
| } |
| } |
| if (subdevice != 0) { |
| err = snd_config_integer_add(control, "subdevice", subdevice); |
| if (err < 0) { |
| error("snd_config_integer_add: %s", snd_strerror(err)); |
| return err; |
| } |
| } |
| err = snd_config_string_add(control, "name", name); |
| if (err < 0) { |
| error("snd_config_string_add: %s", snd_strerror(err)); |
| return err; |
| } |
| if (index != 0) { |
| err = snd_config_integer_add(control, "index", index); |
| if (err < 0) { |
| error("snd_config_integer_add: %s", snd_strerror(err)); |
| return err; |
| } |
| } |
| |
| switch (type) { |
| case SND_CTL_ELEM_TYPE_BYTES: |
| case SND_CTL_ELEM_TYPE_IEC958: |
| { |
| size_t size = type == SND_CTL_ELEM_TYPE_BYTES ? |
| count : sizeof(snd_aes_iec958_t); |
| char buf[size * 2 + 1]; |
| char *p = buf; |
| char *hex = "0123456789abcdef"; |
| const unsigned char *bytes = |
| (const unsigned char *)snd_ctl_elem_value_get_bytes(ctl); |
| for (idx = 0; idx < size; idx++) { |
| int v = bytes[idx]; |
| *p++ = hex[v >> 4]; |
| *p++ = hex[v & 0x0f]; |
| } |
| *p = '\0'; |
| err = snd_config_string_add(control, "value", buf); |
| if (err < 0) { |
| error("snd_config_string_add: %s", snd_strerror(err)); |
| return err; |
| } |
| goto finish; |
| } |
| default: |
| break; |
| } |
| |
| if (count == 1) { |
| switch (type) { |
| case SND_CTL_ELEM_TYPE_BOOLEAN: |
| err = snd_config_string_add(control, "value", snd_ctl_elem_value_get_boolean(ctl, 0) ? "true" : "false"); |
| if (err < 0) { |
| error("snd_config_string_add: %s", snd_strerror(err)); |
| return err; |
| } |
| goto finish; |
| case SND_CTL_ELEM_TYPE_INTEGER: |
| err = snd_config_integer_add(control, "value", snd_ctl_elem_value_get_integer(ctl, 0)); |
| if (err < 0) { |
| error("snd_config_integer_add: %s", snd_strerror(err)); |
| return err; |
| } |
| goto finish; |
| case SND_CTL_ELEM_TYPE_INTEGER64: |
| err = snd_config_integer64_add(control, "value", snd_ctl_elem_value_get_integer64(ctl, 0)); |
| if (err < 0) { |
| error("snd_config_integer64_add: %s", snd_strerror(err)); |
| return err; |
| } |
| goto finish; |
| case SND_CTL_ELEM_TYPE_ENUMERATED: |
| { |
| unsigned int v = snd_ctl_elem_value_get_enumerated(ctl, 0); |
| snd_config_t *c; |
| err = snd_config_search(item, num_str(v), &c); |
| if (err == 0) { |
| err = snd_config_get_string(c, &s); |
| assert(err == 0); |
| err = snd_config_string_add(control, "value", s); |
| } else { |
| err = snd_config_integer_add(control, "value", v); |
| } |
| if (err < 0) |
| error("snd_config add: %s", snd_strerror(err)); |
| goto finish; |
| } |
| default: |
| error("Unknown control type: %d\n", type); |
| return -EINVAL; |
| } |
| } |
| |
| err = snd_config_compound_add(control, "value", 1, &value); |
| if (err < 0) { |
| error("snd_config_compound_add: %s", snd_strerror(err)); |
| return err; |
| } |
| |
| switch (type) { |
| case SND_CTL_ELEM_TYPE_BOOLEAN: |
| for (idx = 0; idx < count; idx++) { |
| err = snd_config_string_add(value, num_str(idx), snd_ctl_elem_value_get_boolean(ctl, idx) ? "true" : "false"); |
| if (err < 0) { |
| error("snd_config_string_add: %s", snd_strerror(err)); |
| return err; |
| } |
| } |
| break; |
| case SND_CTL_ELEM_TYPE_INTEGER: |
| for (idx = 0; idx < count; idx++) { |
| err = snd_config_integer_add(value, num_str(idx), snd_ctl_elem_value_get_integer(ctl, idx)); |
| if (err < 0) { |
| error("snd_config_integer_add: %s", snd_strerror(err)); |
| return err; |
| } |
| } |
| break; |
| case SND_CTL_ELEM_TYPE_INTEGER64: |
| for (idx = 0; idx < count; idx++) { |
| err = snd_config_integer64_add(value, num_str(idx), snd_ctl_elem_value_get_integer64(ctl, idx)); |
| if (err < 0) { |
| error("snd_config_integer64_add: %s", snd_strerror(err)); |
| return err; |
| } |
| } |
| break; |
| case SND_CTL_ELEM_TYPE_ENUMERATED: |
| for (idx = 0; idx < count; idx++) { |
| unsigned int v = snd_ctl_elem_value_get_enumerated(ctl, idx); |
| snd_config_t *c; |
| err = snd_config_search(item, num_str(v), &c); |
| if (err == 0) { |
| err = snd_config_get_string(c, &s); |
| assert(err == 0); |
| err = snd_config_string_add(value, num_str(idx), s); |
| } else { |
| err = snd_config_integer_add(value, num_str(idx), v); |
| } |
| if (err < 0) { |
| error("snd_config add: %s", snd_strerror(err)); |
| return err; |
| } |
| } |
| break; |
| default: |
| error("Unknown control type: %d\n", type); |
| return -EINVAL; |
| } |
| |
| finish: |
| err = snd_config_add(control, comment); |
| if (err < 0) { |
| error("snd_config_add: %s", snd_strerror(err)); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int get_controls(int cardno, snd_config_t *top) |
| { |
| snd_ctl_t *handle; |
| snd_ctl_card_info_t *info; |
| snd_config_t *state, *card, *control; |
| snd_ctl_elem_list_t *list; |
| snd_ctl_elem_id_t *elem_id; |
| unsigned int idx; |
| int err; |
| char name[32]; |
| unsigned int count; |
| const char *id; |
| snd_ctl_card_info_alloca(&info); |
| snd_ctl_elem_list_alloca(&list); |
| snd_ctl_elem_id_alloca(&elem_id); |
| |
| sprintf(name, "hw:%d", cardno); |
| err = snd_ctl_open(&handle, name, SND_CTL_READONLY); |
| if (err < 0) { |
| error("snd_ctl_open error: %s", snd_strerror(err)); |
| return err; |
| } |
| err = snd_ctl_card_info(handle, info); |
| if (err < 0) { |
| error("snd_ctl_card_info error: %s", snd_strerror(err)); |
| goto _close; |
| } |
| id = snd_ctl_card_info_get_id(info); |
| err = snd_config_search(top, "state", &state); |
| if (err == 0 && |
| snd_config_get_type(state) != SND_CONFIG_TYPE_COMPOUND) { |
| error("config state node is not a compound"); |
| err = -EINVAL; |
| goto _close; |
| } |
| if (err < 0) { |
| err = snd_config_compound_add(top, "state", 1, &state); |
| if (err < 0) { |
| error("snd_config_compound_add: %s", snd_strerror(err)); |
| goto _close; |
| } |
| } |
| err = snd_config_search(state, id, &card); |
| if (err == 0 && |
| snd_config_get_type(card) != SND_CONFIG_TYPE_COMPOUND) { |
| error("config state.%s node is not a compound", id); |
| err = -EINVAL; |
| goto _close; |
| } |
| if (err < 0) { |
| err = snd_config_compound_add(state, id, 0, &card); |
| if (err < 0) { |
| error("snd_config_compound_add: %s", snd_strerror(err)); |
| goto _close; |
| } |
| } |
| err = snd_config_search(card, "control", &control); |
| if (err == 0) { |
| err = snd_config_delete(control); |
| if (err < 0) { |
| error("snd_config_delete: %s", snd_strerror(err)); |
| goto _close; |
| } |
| } |
| err = snd_ctl_elem_list(handle, list); |
| if (err < 0) { |
| error("Cannot determine controls: %s", snd_strerror(err)); |
| goto _close; |
| } |
| count = snd_ctl_elem_list_get_count(list); |
| err = snd_config_compound_add(card, "control", count > 0, &control); |
| if (err < 0) { |
| error("snd_config_compound_add: %s", snd_strerror(err)); |
| goto _close; |
| } |
| if (count == 0) { |
| err = 0; |
| goto _close; |
| } |
| snd_ctl_elem_list_set_offset(list, 0); |
| if (snd_ctl_elem_list_alloc_space(list, count) < 0) { |
| error("No enough memory..."); |
| goto _close; |
| } |
| if ((err = snd_ctl_elem_list(handle, list)) < 0) { |
| error("Cannot determine controls (2): %s", snd_strerror(err)); |
| goto _free; |
| } |
| for (idx = 0; idx < count; ++idx) { |
| snd_ctl_elem_list_get_id(list, idx, elem_id); |
| err = get_control(handle, elem_id, control); |
| if (err < 0) |
| goto _free; |
| } |
| |
| err = 0; |
| _free: |
| snd_ctl_elem_list_free_space(list); |
| _close: |
| snd_ctl_close(handle); |
| return err; |
| } |
| |
| static long config_iface(snd_config_t *n) |
| { |
| long i; |
| long long li; |
| snd_ctl_elem_iface_t idx; |
| const char *str; |
| switch (snd_config_get_type(n)) { |
| case SND_CONFIG_TYPE_INTEGER: |
| snd_config_get_integer(n, &i); |
| return i; |
| case SND_CONFIG_TYPE_INTEGER64: |
| snd_config_get_integer64(n, &li); |
| return li; |
| case SND_CONFIG_TYPE_STRING: |
| snd_config_get_string(n, &str); |
| break; |
| default: |
| return -1; |
| } |
| for (idx = 0; idx <= SND_CTL_ELEM_IFACE_LAST; idx++) { |
| if (strcasecmp(snd_ctl_elem_iface_name(idx), str) == 0) |
| return idx; |
| } |
| return -1; |
| } |
| |
| static int config_bool(snd_config_t *n, int doit) |
| { |
| const char *str; |
| long val; |
| long long lval; |
| |
| switch (snd_config_get_type(n)) { |
| case SND_CONFIG_TYPE_INTEGER: |
| snd_config_get_integer(n, &val); |
| if (val < 0 || val > 1) |
| return -1; |
| return val; |
| case SND_CONFIG_TYPE_INTEGER64: |
| snd_config_get_integer64(n, &lval); |
| if (lval < 0 || lval > 1) |
| return -1; |
| return (int) lval; |
| case SND_CONFIG_TYPE_STRING: |
| snd_config_get_string(n, &str); |
| break; |
| case SND_CONFIG_TYPE_COMPOUND: |
| if (!force_restore || !doit) |
| return -1; |
| n = snd_config_iterator_entry(snd_config_iterator_first(n)); |
| return config_bool(n, doit); |
| default: |
| return -1; |
| } |
| if (strcmp(str, "on") == 0 || strcmp(str, "true") == 0) |
| return 1; |
| if (strcmp(str, "off") == 0 || strcmp(str, "false") == 0) |
| return 0; |
| return -1; |
| } |
| |
| static int config_enumerated(snd_config_t *n, snd_ctl_t *handle, |
| snd_ctl_elem_info_t *info, int doit) |
| { |
| const char *str; |
| long val; |
| long long lval; |
| unsigned int idx, items; |
| |
| switch (snd_config_get_type(n)) { |
| case SND_CONFIG_TYPE_INTEGER: |
| snd_config_get_integer(n, &val); |
| return val; |
| case SND_CONFIG_TYPE_INTEGER64: |
| snd_config_get_integer64(n, &lval); |
| return (int) lval; |
| case SND_CONFIG_TYPE_STRING: |
| snd_config_get_string(n, &str); |
| break; |
| case SND_CONFIG_TYPE_COMPOUND: |
| if (!force_restore || !doit) |
| return -1; |
| n = snd_config_iterator_entry(snd_config_iterator_first(n)); |
| return config_enumerated(n, handle, info, doit); |
| default: |
| return -1; |
| } |
| items = snd_ctl_elem_info_get_items(info); |
| for (idx = 0; idx < items; idx++) { |
| int err; |
| snd_ctl_elem_info_set_item(info, idx); |
| err = snd_ctl_elem_info(handle, info); |
| if (err < 0) { |
| error("snd_ctl_elem_info: %s", snd_strerror(err)); |
| return err; |
| } |
| if (strcmp(str, snd_ctl_elem_info_get_item_name(info)) == 0) |
| return idx; |
| } |
| return -1; |
| } |
| |
| static int config_integer(snd_config_t *n, long *val, int doit) |
| { |
| int err = snd_config_get_integer(n, val); |
| if (err < 0 && force_restore && doit) { |
| if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) |
| return err; |
| n = snd_config_iterator_entry(snd_config_iterator_first(n)); |
| return config_integer(n, val, doit); |
| } |
| return err; |
| } |
| |
| static int config_integer64(snd_config_t *n, long long *val, int doit) |
| { |
| int err = snd_config_get_integer64(n, val); |
| if (err < 0 && force_restore && doit) { |
| if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) |
| return err; |
| n = snd_config_iterator_entry(snd_config_iterator_first(n)); |
| return config_integer64(n, val, doit); |
| } |
| return err; |
| } |
| |
| static int check_comment_access(snd_config_t *conf, const char *str) |
| { |
| snd_config_iterator_t i, next; |
| |
| snd_config_for_each(i, next, conf) { |
| snd_config_t *n = snd_config_iterator_entry(i); |
| const char *id, *s; |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| if (strcmp(id, "access") == 0) { |
| if (snd_config_get_string(n, &s) < 0) |
| return 0; |
| if (strstr(s, str)) |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| /* |
| * get the item type from the given comment config |
| */ |
| static int get_comment_type(snd_config_t *n) |
| { |
| static const snd_ctl_elem_type_t types[] = { |
| SND_CTL_ELEM_TYPE_BOOLEAN, |
| SND_CTL_ELEM_TYPE_INTEGER, |
| SND_CTL_ELEM_TYPE_ENUMERATED, |
| SND_CTL_ELEM_TYPE_BYTES, |
| SND_CTL_ELEM_TYPE_IEC958, |
| SND_CTL_ELEM_TYPE_INTEGER64, |
| }; |
| const char *type; |
| unsigned int i; |
| |
| if (snd_config_get_string(n, &type) < 0) |
| return -EINVAL; |
| for (i = 0; i < ARRAY_SIZE(types); ++i) |
| if (strcmp(type, snd_ctl_elem_type_name(types[i])) == 0) |
| return types[i]; |
| return -EINVAL; |
| } |
| |
| /* |
| * get the value range from the given comment config |
| */ |
| static int get_comment_range(snd_config_t *n, int ctype, |
| long *imin, long *imax, long *istep) |
| { |
| const char *s; |
| int err; |
| |
| if (snd_config_get_string(n, &s) < 0) |
| return -EINVAL; |
| switch (ctype) { |
| case SND_CTL_ELEM_TYPE_INTEGER: |
| err = sscanf(s, "%li - %li (step %li)", imin, imax, istep); |
| if (err != 3) { |
| istep = 0; |
| err = sscanf(s, "%li - %li", imin, imax); |
| if (err != 2) |
| return -EINVAL; |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| struct string_array { |
| unsigned int count; |
| const char **strings; |
| }; |
| |
| static int get_comment_items(snd_config_t *n, struct string_array *items) |
| { |
| snd_config_iterator_t it, next; |
| unsigned int i; |
| int err; |
| |
| snd_config_for_each(it, next, n) { |
| snd_config_t *item = snd_config_iterator_entry(it); |
| const char *id; |
| unsigned int numid; |
| |
| if (snd_config_get_id(item, &id) < 0) |
| return -EINVAL; |
| numid = atoi(id); |
| if (numid > 999999) |
| return -EINVAL; |
| |
| if (numid >= items->count) { |
| const char **strings = realloc(items->strings, (numid + 1) * sizeof(char *)); |
| if (!strings) |
| return -ENOMEM; |
| for (i = items->count; i < numid + 1; ++i) |
| strings[i] = NULL; |
| items->count = numid + 1; |
| items->strings = strings; |
| } |
| err = snd_config_get_string(item, &items->strings[numid]); |
| if (err < 0) |
| return err; |
| } |
| |
| for (i = 0; i < items->count; ++i) |
| if (!items->strings[i]) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int add_user_control(snd_ctl_t *handle, snd_ctl_elem_info_t *info, snd_config_t *conf) |
| { |
| snd_ctl_elem_id_t *id; |
| snd_config_iterator_t i, next; |
| long imin, imax, istep; |
| snd_ctl_elem_type_t ctype; |
| unsigned int count; |
| struct string_array enum_items; |
| int err; |
| unsigned int *tlv; |
| |
| imin = imax = istep = 0; |
| count = 0; |
| ctype = SND_CTL_ELEM_TYPE_NONE; |
| enum_items.count = 0; |
| enum_items.strings = NULL; |
| tlv = NULL; |
| snd_config_for_each(i, next, conf) { |
| snd_config_t *n = snd_config_iterator_entry(i); |
| const char *id; |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| if (strcmp(id, "type") == 0) { |
| err = get_comment_type(n); |
| if (err < 0) |
| goto error; |
| ctype = err; |
| continue; |
| } |
| if (strcmp(id, "range") == 0) { |
| err = get_comment_range(n, ctype, &imin, &imax, &istep); |
| if (err < 0) |
| goto error; |
| continue; |
| } |
| if (strcmp(id, "count") == 0) { |
| long v; |
| if ((err = snd_config_get_integer(n, &v)) < 0) |
| goto error; |
| count = v; |
| continue; |
| } |
| if (strcmp(id, "item") == 0) { |
| err = get_comment_items(n, &enum_items); |
| if (err < 0) |
| goto error; |
| continue; |
| } |
| if (strcmp(id, "tlv") == 0) { |
| const char *s; |
| if ((err = snd_config_get_string(n, &s)) < 0) |
| goto error; |
| if (tlv) |
| free(tlv); |
| if ((tlv = str_to_tlv(s)) == NULL) { |
| err = -EINVAL; |
| goto error; |
| } |
| continue; |
| } |
| } |
| |
| snd_ctl_elem_id_alloca(&id); |
| snd_ctl_elem_info_get_id(info, id); |
| if (count <= 0) |
| count = 1; |
| switch (ctype) { |
| case SND_CTL_ELEM_TYPE_INTEGER: |
| if (imin > imax || istep > imax - imin) { |
| err = -EINVAL; |
| goto error; |
| } |
| err = snd_ctl_elem_add_integer(handle, id, count, imin, imax, istep); |
| if (err < 0) |
| goto error; |
| if (tlv) |
| snd_ctl_elem_tlv_write(handle, id, tlv); |
| break; |
| case SND_CTL_ELEM_TYPE_BOOLEAN: |
| err = snd_ctl_elem_add_boolean(handle, id, count); |
| break; |
| case SND_CTL_ELEM_TYPE_ENUMERATED: |
| err = snd_ctl_elem_add_enumerated(handle, id, count, |
| enum_items.count, enum_items.strings); |
| break; |
| case SND_CTL_ELEM_TYPE_IEC958: |
| err = snd_ctl_elem_add_iec958(handle, id); |
| break; |
| default: |
| err = -EINVAL; |
| break; |
| } |
| |
| error: |
| free(tlv); |
| free(enum_items.strings); |
| if (err < 0) |
| return err; |
| return snd_ctl_elem_info(handle, info); |
| } |
| |
| /* |
| * check whether the config item has the same of compatible type |
| */ |
| static int check_comment_type(snd_config_t *conf, int type) |
| { |
| snd_config_t *n; |
| int ctype; |
| |
| if (snd_config_search(conf, "type", &n) < 0) |
| return 0; /* not defined */ |
| ctype = get_comment_type(n); |
| if (ctype == type) |
| return 0; |
| if ((ctype == SND_CTL_ELEM_TYPE_BOOLEAN || |
| ctype == SND_CTL_ELEM_TYPE_INTEGER || |
| ctype == SND_CTL_ELEM_TYPE_INTEGER64 || |
| ctype == SND_CTL_ELEM_TYPE_ENUMERATED) && |
| (type == SND_CTL_ELEM_TYPE_BOOLEAN || |
| type == SND_CTL_ELEM_TYPE_INTEGER || |
| type == SND_CTL_ELEM_TYPE_INTEGER64 || |
| type == SND_CTL_ELEM_TYPE_ENUMERATED)) |
| return 0; /* OK, compatible */ |
| return -EINVAL; |
| } |
| |
| /* |
| * convert from an old value to a new value with the same dB level |
| */ |
| static int convert_to_new_db(snd_config_t *value, long omin, long omax, |
| long nmin, long nmax, |
| long odbmin, long odbmax, |
| snd_config_t *comment, const char *index, |
| snd_ctl_t *device, snd_ctl_elem_id_t *id, |
| int doit) |
| { |
| snd_config_t *db_node; |
| long db, val; |
| int err; |
| |
| if (snd_config_searchv(comment, &db_node, "dbvalue", index, NULL) < 0 || |
| snd_config_get_integer(db_node, &db) < 0) { |
| err = config_integer(value, &val, doit); |
| if (err < 0) |
| return err; |
| if (val < omin || val > omax) |
| return -EINVAL; |
| db = ((val - omin) * (odbmax - odbmin)) / (omax - omin) + odbmin; |
| } |
| |
| err = snd_ctl_convert_from_dB(device, id, db, &val, db > 0); |
| if (err < 0) |
| return err; |
| if (val < nmin) |
| val = nmin; |
| else if (val > nmax) |
| val = nmax; |
| return snd_config_set_integer(value, val); |
| } |
| |
| /* |
| * compare the current value range with the old range in comments. |
| * also, if dB information is available, try to compare them. |
| * if any change occurs, try to keep the same dB level. |
| */ |
| static int check_comment_range(snd_ctl_t *handle, snd_config_t *conf, |
| snd_ctl_elem_info_t *info, snd_config_t *value, |
| int doit) |
| { |
| snd_config_t *n; |
| long omin, omax, ostep; |
| long nmin, nmax; |
| long odbmin, odbmax; |
| long ndbmin, ndbmax; |
| snd_ctl_elem_id_t *id; |
| |
| if (snd_config_search(conf, "range", &n) < 0) |
| return 0; |
| if (get_comment_range(n, SND_CTL_ELEM_TYPE_INTEGER, |
| &omin, &omax, &ostep) < 0) |
| return 0; |
| nmin = snd_ctl_elem_info_get_min(info); |
| nmax = snd_ctl_elem_info_get_max(info); |
| if (omin != nmin && omax != nmax) { |
| /* Hey, the range mismatches */ |
| if (!force_restore || !doit) |
| return -EINVAL; |
| } |
| if (omin >= omax || nmin >= nmax) |
| return 0; /* invalid values */ |
| |
| if (snd_config_search(conf, "dbmin", &n) < 0) |
| return 0; |
| if (config_integer(n, &odbmin, doit) < 0) |
| return 0; |
| if (snd_config_search(conf, "dbmax", &n) < 0) |
| return 0; |
| if (config_integer(n, &odbmax, doit) < 0) |
| return 0; |
| if (odbmin >= odbmax) |
| return 0; /* invalid values */ |
| snd_ctl_elem_id_alloca(&id); |
| snd_ctl_elem_info_get_id(info, id); |
| if (snd_ctl_get_dB_range(handle, id, &ndbmin, &ndbmax) < 0) |
| return 0; |
| if (ndbmin >= ndbmax) |
| return 0; /* invalid values */ |
| if (omin == nmin && omax == nmax && |
| odbmin == ndbmin && odbmax == ndbmax) |
| return 0; /* OK, identical one */ |
| |
| /* Let's guess the current value from dB range */ |
| if (snd_config_get_type(value) == SND_CONFIG_TYPE_COMPOUND) { |
| snd_config_iterator_t i, next; |
| snd_config_for_each(i, next, value) { |
| snd_config_t *n = snd_config_iterator_entry(i); |
| const char *idxstr; |
| if (snd_config_get_id(n, &idxstr) < 0) |
| continue; |
| convert_to_new_db(n, omin, omax, nmin, nmax, |
| odbmin, odbmax, conf, idxstr, |
| handle, id, doit); |
| } |
| } else |
| convert_to_new_db(value, omin, omax, nmin, nmax, |
| odbmin, odbmax, conf, "0", |
| handle, id, doit); |
| return 0; |
| } |
| |
| static int restore_config_value(snd_ctl_t *handle, snd_ctl_elem_info_t *info, |
| snd_ctl_elem_iface_t type, |
| snd_config_t *value, |
| snd_ctl_elem_value_t *ctl, int idx, |
| int doit) |
| { |
| long val; |
| long long lval; |
| int err; |
| |
| switch (type) { |
| case SND_CTL_ELEM_TYPE_BOOLEAN: |
| val = config_bool(value, doit); |
| if (val >= 0) { |
| snd_ctl_elem_value_set_boolean(ctl, idx, val); |
| return 1; |
| } |
| break; |
| case SND_CTL_ELEM_TYPE_INTEGER: |
| err = config_integer(value, &val, doit); |
| if (err == 0) { |
| snd_ctl_elem_value_set_integer(ctl, idx, val); |
| return 1; |
| } |
| break; |
| case SND_CTL_ELEM_TYPE_INTEGER64: |
| err = config_integer64(value, &lval, doit); |
| if (err == 0) { |
| snd_ctl_elem_value_set_integer64(ctl, idx, lval); |
| return 1; |
| } |
| break; |
| case SND_CTL_ELEM_TYPE_ENUMERATED: |
| val = config_enumerated(value, handle, info, doit); |
| if (val >= 0) { |
| snd_ctl_elem_value_set_enumerated(ctl, idx, val); |
| return 1; |
| } |
| break; |
| case SND_CTL_ELEM_TYPE_BYTES: |
| case SND_CTL_ELEM_TYPE_IEC958: |
| break; |
| default: |
| cerror(doit, "Unknow control type: %d", type); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int restore_config_value2(snd_ctl_t *handle, snd_ctl_elem_info_t *info, |
| snd_ctl_elem_iface_t type, |
| snd_config_t *value, |
| snd_ctl_elem_value_t *ctl, int idx, |
| unsigned int numid, int doit) |
| { |
| int err = restore_config_value(handle, info, type, value, ctl, idx, doit); |
| long val; |
| |
| if (err != 0) |
| return err; |
| switch (type) { |
| case SND_CTL_ELEM_TYPE_BYTES: |
| case SND_CTL_ELEM_TYPE_IEC958: |
| err = snd_config_get_integer(value, &val); |
| if (err < 0 || val < 0 || val > 255) { |
| cerror(doit, "bad control.%d.value.%d content", numid, idx); |
| return force_restore && doit ? 0 : -EINVAL; |
| } |
| snd_ctl_elem_value_set_byte(ctl, idx, val); |
| return 1; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static int set_control(snd_ctl_t *handle, snd_config_t *control, |
| int *maxnumid, int doit) |
| { |
| snd_ctl_elem_value_t *ctl; |
| snd_ctl_elem_info_t *info; |
| snd_config_iterator_t i, next; |
| unsigned int numid1; |
| snd_ctl_elem_iface_t iface = -1; |
| int iface1; |
| const char *name1; |
| unsigned int numid; |
| snd_ctl_elem_type_t type; |
| unsigned int count; |
| long device = -1; |
| long device1; |
| long subdevice = -1; |
| long subdevice1; |
| const char *name = NULL; |
| long index1; |
| long index = -1; |
| snd_config_t *value = NULL; |
| snd_config_t *comment = NULL; |
| unsigned int idx; |
| int err; |
| char *set; |
| const char *id; |
| snd_ctl_elem_value_alloca(&ctl); |
| snd_ctl_elem_info_alloca(&info); |
| if (snd_config_get_type(control) != SND_CONFIG_TYPE_COMPOUND) { |
| cerror(doit, "control is not a compound"); |
| return -EINVAL; |
| } |
| err = snd_config_get_id(control, &id); |
| if (err < 0) { |
| cerror(doit, "unable to get id"); |
| return -EINVAL; |
| } |
| numid = atoi(id); |
| if ((int)numid > *maxnumid) |
| *maxnumid = numid; |
| snd_config_for_each(i, next, control) { |
| snd_config_t *n = snd_config_iterator_entry(i); |
| const char *fld; |
| if (snd_config_get_id(n, &fld) < 0) |
| continue; |
| if (strcmp(fld, "comment") == 0) { |
| if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) { |
| cerror(doit, "control.%d.%s is invalid", numid, fld); |
| return -EINVAL; |
| } |
| comment = n; |
| continue; |
| } |
| if (strcmp(fld, "iface") == 0) { |
| iface = (snd_ctl_elem_iface_t)config_iface(n); |
| continue; |
| } |
| if (strcmp(fld, "device") == 0) { |
| if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) { |
| cerror(doit, "control.%d.%s is invalid", numid, fld); |
| return -EINVAL; |
| } |
| snd_config_get_integer(n, &device); |
| continue; |
| } |
| if (strcmp(fld, "subdevice") == 0) { |
| if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) { |
| cerror(doit, "control.%d.%s is invalid", numid, fld); |
| return -EINVAL; |
| } |
| snd_config_get_integer(n, &subdevice); |
| continue; |
| } |
| if (strcmp(fld, "name") == 0) { |
| if (snd_config_get_type(n) != SND_CONFIG_TYPE_STRING) { |
| cerror(doit, "control.%d.%s is invalid", numid, fld); |
| return -EINVAL; |
| } |
| snd_config_get_string(n, &name); |
| continue; |
| } |
| if (strcmp(fld, "index") == 0) { |
| if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) { |
| cerror(doit, "control.%d.%s is invalid", numid, fld); |
| return -EINVAL; |
| } |
| snd_config_get_integer(n, &index); |
| continue; |
| } |
| if (strcmp(fld, "value") == 0) { |
| value = n; |
| continue; |
| } |
| cerror(doit, "unknown control.%d.%s field", numid, fld); |
| } |
| if (!value) { |
| cerror(doit, "missing control.%d.value", numid); |
| return -EINVAL; |
| } |
| if (device < 0) |
| device = 0; |
| if (subdevice < 0) |
| subdevice = 0; |
| if (index < 0) |
| index = 0; |
| |
| err = -EINVAL; |
| if (!force_restore) { |
| snd_ctl_elem_info_set_numid(info, numid); |
| err = snd_ctl_elem_info(handle, info); |
| } |
| if (err < 0 && name) { |
| snd_ctl_elem_info_set_numid(info, 0); |
| snd_ctl_elem_info_set_interface(info, iface); |
| snd_ctl_elem_info_set_device(info, device); |
| snd_ctl_elem_info_set_subdevice(info, subdevice); |
| snd_ctl_elem_info_set_name(info, name); |
| snd_ctl_elem_info_set_index(info, index); |
| err = snd_ctl_elem_info(handle, info); |
| if (err < 0 && comment && check_comment_access(comment, "user")) { |
| err = add_user_control(handle, info, comment); |
| if (err < 0) { |
| cerror(doit, "failed to add user control #%d (%s)", |
| numid, snd_strerror(err)); |
| return err; |
| } |
| } |
| } |
| if (err < 0) { |
| cerror(doit, "failed to obtain info for control #%d (%s)", numid, snd_strerror(err)); |
| return -ENOENT; |
| } |
| numid1 = snd_ctl_elem_info_get_numid(info); |
| iface1 = snd_ctl_elem_info_get_interface(info); |
| device1 = snd_ctl_elem_info_get_device(info); |
| subdevice1 = snd_ctl_elem_info_get_subdevice(info); |
| name1 = snd_ctl_elem_info_get_name(info); |
| index1 = snd_ctl_elem_info_get_index(info); |
| count = snd_ctl_elem_info_get_count(info); |
| type = snd_ctl_elem_info_get_type(info); |
| if (err |= numid != numid1 && !force_restore) |
| cerror(doit, "warning: numid mismatch (%d/%d) for control #%d", |
| numid, numid1, numid); |
| if (err |= iface != iface1) |
| cerror(doit, "warning: iface mismatch (%d/%d) for control #%d", iface, iface1, numid); |
| if (err |= device != device1) |
| cerror(doit, "warning: device mismatch (%ld/%ld) for control #%d", device, device1, numid); |
| if (err |= subdevice != subdevice1) |
| cerror(doit, "warning: subdevice mismatch (%ld/%ld) for control #%d", subdevice, subdevice1, numid); |
| if (err |= strcmp(name, name1)) |
| cerror(doit, "warning: name mismatch (%s/%s) for control #%d", name, name1, numid); |
| if (err |= index != index1) |
| cerror(doit, "warning: index mismatch (%ld/%ld) for control #%d", index, index1, numid); |
| if (err < 0) { |
| cerror(doit, "failed to obtain info for control #%d (%s)", numid, snd_strerror(err)); |
| return -ENOENT; |
| } |
| |
| if (comment) { |
| if (check_comment_type(comment, type) < 0) |
| cerror(doit, "incompatible field type for control #%d", numid); |
| if (type == SND_CTL_ELEM_TYPE_INTEGER) { |
| if (check_comment_range(handle, comment, info, value, doit) < 0) { |
| cerror(doit, "value range mismatch for control #%d", |
| numid); |
| return -EINVAL; |
| } |
| } |
| /* inactive controls are not restored */ |
| if (comment && check_comment_access(comment, "inactive")) |
| return 0; |
| } |
| |
| if (snd_ctl_elem_info_is_inactive(info) || |
| !snd_ctl_elem_info_is_writable(info)) |
| return 0; |
| snd_ctl_elem_value_set_numid(ctl, numid1); |
| |
| if (count == 1) { |
| err = restore_config_value(handle, info, type, value, ctl, 0, doit); |
| if (err < 0) |
| return err; |
| if (err > 0) |
| goto _ok; |
| } |
| switch (type) { |
| case SND_CTL_ELEM_TYPE_BYTES: |
| case SND_CTL_ELEM_TYPE_IEC958: |
| { |
| const char *buf; |
| err = snd_config_get_string(value, &buf); |
| if (err >= 0) { |
| int c1 = 0; |
| int len = strlen(buf); |
| unsigned int idx = 0; |
| int size = type == SND_CTL_ELEM_TYPE_BYTES ? |
| count : sizeof(snd_aes_iec958_t); |
| if (size * 2 != len) { |
| cerror(doit, "bad control.%d.value contents\n", numid); |
| return -EINVAL; |
| } |
| while (*buf) { |
| int c = *buf++; |
| if ((c = hextodigit(c)) < 0) { |
| cerror(doit, "bad control.%d.value contents\n", numid); |
| return -EINVAL; |
| } |
| if (idx % 2 == 1) |
| snd_ctl_elem_value_set_byte(ctl, idx / 2, c1 << 4 | c); |
| else |
| c1 = c; |
| idx++; |
| } |
| goto _ok; |
| } |
| } |
| default: |
| break; |
| } |
| if (snd_config_get_type(value) != SND_CONFIG_TYPE_COMPOUND) { |
| if (!force_restore || !doit) { |
| cerror(doit, "bad control.%d.value type", numid); |
| return -EINVAL; |
| } |
| for (idx = 0; idx < count; ++idx) { |
| err = restore_config_value2(handle, info, type, value, |
| ctl, idx, numid, doit); |
| if (err < 0) |
| return err; |
| } |
| goto _ok; |
| } |
| |
| set = (char*) alloca(count); |
| memset(set, 0, count); |
| snd_config_for_each(i, next, value) { |
| snd_config_t *n = snd_config_iterator_entry(i); |
| const char *id; |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| idx = atoi(id); |
| if (idx >= count || set[idx]) { |
| cerror(doit, "bad control.%d.value index", numid); |
| if (!force_restore || !doit) |
| return -EINVAL; |
| continue; |
| } |
| err = restore_config_value2(handle, info, type, n, |
| ctl, idx, numid, doit); |
| if (err < 0) |
| return err; |
| if (err > 0) |
| set[idx] = 1; |
| } |
| for (idx = 0; idx < count; ++idx) { |
| if (!set[idx]) { |
| cerror(doit, "control.%d.value.%d is not specified", numid, idx); |
| if (!force_restore || !doit) |
| return -EINVAL; |
| } |
| } |
| |
| _ok: |
| err = doit ? snd_ctl_elem_write(handle, ctl) : 0; |
| if (err < 0) { |
| error("Cannot write control '%d:%ld:%ld:%s:%ld' : %s", (int)iface, device, subdevice, name, index, snd_strerror(err)); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int set_controls(int card, snd_config_t *top, int doit) |
| { |
| snd_ctl_t *handle; |
| snd_ctl_card_info_t *info; |
| snd_config_t *control; |
| snd_config_iterator_t i, next; |
| int err, maxnumid = -1; |
| char name[32], tmpid[16]; |
| const char *id; |
| snd_ctl_card_info_alloca(&info); |
| |
| sprintf(name, "hw:%d", card); |
| dbg("device='%s', doit=%i", name, doit); |
| err = snd_ctl_open(&handle, name, 0); |
| if (err < 0) { |
| error("snd_ctl_open error: %s", snd_strerror(err)); |
| return err; |
| } |
| err = snd_ctl_card_info(handle, info); |
| if (err < 0) { |
| error("snd_ctl_card_info error: %s", snd_strerror(err)); |
| goto _close; |
| } |
| id = snd_ctl_card_info_get_id(info); |
| dbg("card-info-id: '%s'", id); |
| err = snd_config_searchv(top, &control, "state", id, "control", 0); |
| if (err < 0) { |
| if (force_restore) { |
| sprintf(tmpid, "card%d", card); |
| err = snd_config_searchv(top, &control, "state", tmpid, "control", 0); |
| if (! err) |
| id = tmpid; |
| } |
| if (err < 0) { |
| fprintf(stderr, "No state is present for card %s\n", id); |
| goto _close; |
| } |
| id = tmpid; |
| } |
| if (snd_config_get_type(control) != SND_CONFIG_TYPE_COMPOUND) { |
| cerror(doit, "state.%s.control is not a compound\n", id); |
| return -EINVAL; |
| } |
| snd_config_for_each(i, next, control) { |
| snd_config_t *n = snd_config_iterator_entry(i); |
| err = set_control(handle, n, &maxnumid, doit); |
| if (err < 0 && (!force_restore || !doit)) |
| goto _close; |
| } |
| |
| dbg("maxnumid=%i", maxnumid); |
| /* check if we have additional controls in driver */ |
| /* in this case we should go through init procedure */ |
| if (!doit && maxnumid >= 0) { |
| snd_ctl_elem_info_t *info; |
| snd_ctl_elem_info_alloca(&info); |
| snd_ctl_elem_info_set_numid(info, maxnumid+1); |
| if (snd_ctl_elem_info(handle, info) == 0) { |
| /* not very informative */ |
| /* but value is used for check only */ |
| err = -EAGAIN; |
| dbg("more controls than maxnumid?"); |
| goto _close; |
| } |
| } |
| |
| _close: |
| snd_ctl_close(handle); |
| dbg("result code: %i", err); |
| return err; |
| } |
| |
| int save_state(const char *file, const char *cardname) |
| { |
| int err; |
| snd_config_t *config; |
| snd_input_t *in; |
| snd_output_t *out; |
| int stdio; |
| char *nfile = NULL; |
| |
| err = snd_config_top(&config); |
| if (err < 0) { |
| error("snd_config_top error: %s", snd_strerror(err)); |
| return err; |
| } |
| stdio = !strcmp(file, "-"); |
| if (!stdio) { |
| nfile = malloc(strlen(file) + 5); |
| if (nfile == NULL) { |
| error("No enough memory..."); |
| goto out; |
| } |
| strcpy(nfile, file); |
| strcat(nfile, ".new"); |
| if (state_lock(file, 1, 10) != 0) |
| goto out; |
| } |
| if (!stdio && (err = snd_input_stdio_open(&in, file, "r")) >= 0) { |
| err = snd_config_load(config, in); |
| snd_input_close(in); |
| #if 0 |
| if (err < 0) { |
| error("snd_config_load error: %s", snd_strerror(err)); |
| goto out; |
| } |
| #endif |
| } |
| |
| if (!cardname) { |
| int card, first = 1; |
| |
| card = -1; |
| /* find each installed soundcards */ |
| while (1) { |
| if (snd_card_next(&card) < 0) |
| break; |
| if (card < 0) { |
| if (first) { |
| if (ignore_nocards) { |
| err = 0; |
| goto out; |
| } else { |
| error("No soundcards found..."); |
| err = -ENODEV; |
| goto out; |
| } |
| } |
| break; |
| } |
| first = 0; |
| if ((err = get_controls(card, config))) |
| goto out; |
| } |
| } else { |
| int cardno; |
| |
| cardno = snd_card_get_index(cardname); |
| if (cardno < 0) { |
| error("Cannot find soundcard '%s'...", cardname); |
| err = cardno; |
| goto out; |
| } |
| if ((err = get_controls(cardno, config))) { |
| goto out; |
| } |
| } |
| |
| if (stdio) { |
| err = snd_output_stdio_attach(&out, stdout, 0); |
| } else { |
| err = snd_output_stdio_open(&out, nfile, "w"); |
| } |
| if (err < 0) { |
| error("Cannot open %s for writing: %s", file, snd_strerror(err)); |
| err = -errno; |
| goto out; |
| } |
| err = snd_config_save(config, out); |
| snd_output_close(out); |
| if (err < 0) { |
| error("snd_config_save: %s", snd_strerror(err)); |
| } else { |
| err = rename(nfile, file); |
| if (err < 0) |
| error("rename failed: %s (%s)", strerror(-err), file); |
| } |
| out: |
| if (!stdio) |
| state_lock(file, 0, 10); |
| free(nfile); |
| snd_config_delete(config); |
| snd_config_update_free_global(); |
| return err; |
| } |
| |
| int load_state(const char *file, const char *initfile, const char *cardname, |
| int do_init) |
| { |
| int err, finalerr = 0; |
| snd_config_t *config; |
| snd_input_t *in; |
| int stdio, locked = 0; |
| |
| err = snd_config_top(&config); |
| if (err < 0) { |
| error("snd_config_top error: %s", snd_strerror(err)); |
| return err; |
| } |
| stdio = !strcmp(file, "-"); |
| if (stdio) { |
| err = snd_input_stdio_attach(&in, stdin, 0); |
| } else { |
| err = state_lock(file, 1, 10); |
| locked = err >= 0; |
| err = err >= 0 ? snd_input_stdio_open(&in, file, "r") : err; |
| } |
| if (err >= 0) { |
| err = snd_config_load(config, in); |
| snd_input_close(in); |
| if (locked) |
| state_lock(file, 0, 10); |
| if (err < 0) { |
| error("snd_config_load error: %s", snd_strerror(err)); |
| goto out; |
| } |
| } else { |
| int card, first = 1; |
| char cardname1[16]; |
| |
| error("Cannot open %s for reading: %s", file, snd_strerror(err)); |
| finalerr = err; |
| if (cardname) { |
| card = snd_card_get_index(cardname); |
| if (card < 0) { |
| error("Cannot find soundcard '%s'...", cardname); |
| err = -ENODEV; |
| goto out; |
| } |
| goto single; |
| } else { |
| card = -1; |
| } |
| /* find each installed soundcards */ |
| while (!cardname) { |
| if (snd_card_next(&card) < 0) |
| break; |
| if (card < 0) |
| break; |
| single: |
| first = 0; |
| if (!do_init) |
| break; |
| sprintf(cardname1, "%i", card); |
| err = init(initfile, cardname1); |
| if (err < 0) { |
| finalerr = err; |
| initfailed(card, "init", err); |
| } |
| initfailed(card, "restore", -ENOENT); |
| } |
| if (first) |
| finalerr = 0; /* no cards, no error code */ |
| err = finalerr; |
| goto out; |
| } |
| |
| if (!cardname) { |
| int card, first = 1; |
| char cardname1[16]; |
| |
| card = -1; |
| /* find each installed soundcards */ |
| while (1) { |
| if (snd_card_next(&card) < 0) |
| break; |
| if (card < 0) { |
| if (first) { |
| if (ignore_nocards) { |
| err = 0; |
| goto out; |
| } else { |
| error("No soundcards found..."); |
| err = -ENODEV; |
| goto out; |
| } |
| } |
| break; |
| } |
| first = 0; |
| /* do a check if controls matches state file */ |
| if (do_init && set_controls(card, config, 0)) { |
| sprintf(cardname1, "%i", card); |
| err = init(initfile, cardname1); |
| if (err < 0) { |
| initfailed(card, "init", err); |
| finalerr = err; |
| } |
| } |
| if ((err = set_controls(card, config, 1))) { |
| if (!force_restore) |
| finalerr = err; |
| initfailed(card, "restore", err); |
| } |
| } |
| } else { |
| int cardno; |
| |
| cardno = snd_card_get_index(cardname); |
| if (cardno < 0) { |
| error("Cannot find soundcard '%s'...", cardname); |
| err = -ENODEV; |
| goto out; |
| } |
| /* do a check if controls matches state file */ |
| if (do_init && set_controls(cardno, config, 0)) { |
| err = init(initfile, cardname); |
| if (err < 0) { |
| initfailed(cardno, "init", err); |
| finalerr = err; |
| } |
| } |
| if ((err = set_controls(cardno, config, 1))) { |
| initfailed(cardno, "restore", err); |
| if (!force_restore) |
| goto out; |
| } |
| } |
| err = finalerr; |
| out: |
| snd_config_delete(config); |
| snd_config_update_free_global(); |
| return err; |
| } |