summaryrefslogtreecommitdiffstats
path: root/alsa-utils/alsactl/state.c
diff options
context:
space:
mode:
Diffstat (limited to 'alsa-utils/alsactl/state.c')
-rw-r--r--alsa-utils/alsactl/state.c1653
1 files changed, 1653 insertions, 0 deletions
diff --git a/alsa-utils/alsactl/state.c b/alsa-utils/alsactl/state.c
new file mode 100644
index 0000000..635a999
--- /dev/null
+++ b/alsa-utils/alsactl/state.c
@@ -0,0 +1,1653 @@
+/*
+ * 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"
+
+
+#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a)[0])
+
+
+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_make_integer(&leaf, id);
+ if (err < 0)
+ return err;
+ err = snd_config_add(father, leaf);
+ if (err < 0) {
+ snd_config_delete(leaf);
+ return err;
+ }
+ err = snd_config_set_integer(leaf, integer);
+ 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_make_integer64(&leaf, id);
+ if (err < 0)
+ return err;
+ err = snd_config_add(father, leaf);
+ if (err < 0) {
+ snd_config_delete(leaf);
+ return err;
+ }
+ err = snd_config_set_integer64(leaf, integer);
+ 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_make_string(&leaf, id);
+ if (err < 0)
+ return err;
+ err = snd_config_add(father, leaf);
+ if (err < 0) {
+ snd_config_delete(leaf);
+ return err;
+ }
+ err = snd_config_set_string(leaf, string);
+ 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 and dB ranges 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_config_t *comment)
+{
+ unsigned int tlv[MAX_USER_TLV_SIZE];
+ unsigned int *db;
+ long dbmin, dbmax;
+ 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);
+ if (err < 0) {
+ error("snd_config_string_add: %s", snd_strerror(err));
+ return err;
+ }
+ free(s);
+ }
+ }
+
+ err = snd_tlv_parse_dB_info(tlv, sizeof(tlv), &db);
+ if (err <= 0)
+ return 0;
+
+ snd_tlv_get_dB_range(db, snd_ctl_elem_info_get_min(info),
+ snd_ctl_elem_info_get_max(info),
+ &dbmin, &dbmax);
+ if (err < 0)
+ return err;
+ snd_config_integer_add(comment, "dbmin", dbmin);
+ snd_config_integer_add(comment, "dbmax", dbmax);
+ 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_inactive(info) ||
+ !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_compound_add(control, "comment", 1, &comment);
+ if (err < 0) {
+ error("snd_config_compound_add: %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, 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;
+ }
+ return 0;
+ }
+ 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;
+ }
+ return 0;
+ 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;
+ }
+ return 0;
+ 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;
+ }
+ return 0;
+ 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));
+ return 0;
+ }
+ 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;
+ }
+
+ 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;
+ 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);
+
+ 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_id_t *id;
+ snd_ctl_elem_id_alloca(&id);
+ snd_ctl_elem_list_get_id(list, idx, id);
+ err = get_control(handle, 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 is_user_control(snd_config_t *conf)
+{
+ 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, "user"))
+ 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;
+}
+
+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;
+ int err;
+ unsigned int *tlv;
+
+ imin = imax = istep = 0;
+ count = 0;
+ ctype = SND_CTL_ELEM_TYPE_NONE;
+ 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)
+ return err;
+ ctype = err;
+ continue;
+ }
+ if (strcmp(id, "range") == 0) {
+ err = get_comment_range(n, ctype, &imin, &imax, &istep);
+ if (err < 0)
+ return err;
+ continue;
+ }
+ if (strcmp(id, "count") == 0) {
+ long v;
+ if ((err = snd_config_get_integer(n, &v)) < 0)
+ return err;
+ count = v;
+ continue;
+ }
+ if (strcmp(id, "tlv") == 0) {
+ const char *s;
+ if ((err = snd_config_get_string(n, &s)) < 0)
+ return -EINVAL;
+ if (tlv)
+ free(tlv);
+ if ((tlv = str_to_tlv(s)) == NULL)
+ return -EINVAL;
+ 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)
+ return -EINVAL;
+ 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_IEC958:
+ err = snd_ctl_elem_add_iec958(handle, id);
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ error:
+ free(tlv);
+ if (err < 0)
+ return err;
+ return snd_ctl_elem_info(handle, info);
+}
+
+/*
+ * look for a config node with the given item name
+ */
+static snd_config_t *search_comment_item(snd_config_t *conf, const char *name)
+{
+ 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;
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+ if (strcmp(id, name) == 0)
+ return n;
+ }
+ return NULL;
+}
+
+/*
+ * 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 = search_comment_item(conf, "type");
+ int ctype;
+
+ if (!n)
+ 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,
+ long ndbmin, long ndbmax,
+ int doit)
+{
+ long val;
+ if (config_integer(value, &val, doit) < 0)
+ return -EINVAL;
+ if (val < omin || val > omax)
+ return -EINVAL;
+ val = ((val - omin) * (odbmax - odbmin)) / (omax - omin) + odbmin;
+ if (val < ndbmin)
+ val = ndbmin;
+ else if (val > ndbmax)
+ val = ndbmax;
+ val = ((val - ndbmin) * (nmax - nmin)) / (ndbmax - ndbmin) + nmin;
+ 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;
+
+ n = search_comment_item(conf, "range");
+ if (!n)
+ 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 */
+
+ n = search_comment_item(conf, "dbmin");
+ if (!n)
+ return 0;
+ if (config_integer(n, &odbmin, doit) < 0)
+ return 0;
+ n = search_comment_item(conf, "dbmax");
+ if (!n)
+ 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);
+ convert_to_new_db(n, omin, omax, nmin, nmax,
+ odbmin, odbmax, ndbmin, ndbmax, doit);
+ }
+ } else
+ convert_to_new_db(value, omin, omax, nmin, nmax,
+ odbmin, odbmax, ndbmin, ndbmax, 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;
+ break;
+ 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 && is_user_control(comment)) {
+ 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;
+ }
+ }
+ }
+
+ 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);
+ 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);
+ 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;
+ }
+
+ /* check if we have additional controls in driver */
+ /* in this case we should go through init procedure */
+ if (!doit && maxnumid >= 0) {
+ snd_ctl_elem_id_t *id;
+ snd_ctl_elem_info_t *info;
+ snd_ctl_elem_id_alloca(&id);
+ 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;
+ goto _close;
+ }
+ }
+
+ _close:
+ snd_ctl_close(handle);
+ 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;
+
+ 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_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));
+ return err;
+ }
+#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) {
+ return 0;
+ } else {
+ error("No soundcards found...");
+ return -ENODEV;
+ }
+ }
+ break;
+ }
+ first = 0;
+ if ((err = get_controls(card, config)))
+ return err;
+ }
+ } else {
+ int cardno;
+
+ cardno = snd_card_get_index(cardname);
+ if (cardno < 0) {
+ error("Cannot find soundcard '%s'...", cardname);
+ return cardno;
+ }
+ if ((err = get_controls(cardno, config))) {
+ return err;
+ }
+ }
+
+ if (stdio)
+ err = snd_output_stdio_attach(&out, stdout, 0);
+ else
+ err = snd_output_stdio_open(&out, file, "w");
+ if (err < 0) {
+ error("Cannot open %s for writing: %s", file, snd_strerror(err));
+ return -errno;
+ }
+ err = snd_config_save(config, out);
+ snd_output_close(out);
+ if (err < 0)
+ error("snd_config_save: %s", snd_strerror(err));
+ return 0;
+}
+
+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;
+
+ 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 = snd_input_stdio_open(&in, file, "r");
+ if (err >= 0) {
+ err = snd_config_load(config, in);
+ snd_input_close(in);
+ if (err < 0) {
+ error("snd_config_load error: %s", snd_strerror(err));
+ return err;
+ }
+ } else {
+ int card, first = 1;
+ char cardname1[16];
+
+ error("Cannot open %s for reading: %s", file, snd_strerror(err));
+ finalerr = err;
+ card = -1;
+ /* find each installed soundcards */
+ while (1) {
+ if (snd_card_next(&card) < 0)
+ break;
+ if (card < 0)
+ break;
+ first = 0;
+ if (!do_init)
+ break;
+ sprintf(cardname1, "%i", card);
+ err = init(initfile, cardname1);
+ if (err < 0) {
+ finalerr = err;
+ initfailed(card, "init");
+ }
+ initfailed(card, "restore");
+ }
+ if (first)
+ finalerr = 0; /* no cards, no error code */
+ return finalerr;
+ }
+
+ 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) {
+ return 0;
+ } else {
+ error("No soundcards found...");
+ return -ENODEV;
+ }
+ }
+ 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");
+ finalerr = err;
+ }
+ }
+ if ((err = set_controls(card, config, 1))) {
+ if (!force_restore)
+ finalerr = err;
+ initfailed(card, "restore");
+ }
+ }
+ } else {
+ int cardno;
+
+ cardno = snd_card_get_index(cardname);
+ if (cardno < 0) {
+ error("Cannot find soundcard '%s'...", cardname);
+ return -ENODEV;
+ }
+ /* 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");
+ return err;
+ }
+ }
+ if ((err = set_controls(cardno, config, 1))) {
+ initfailed(cardno, "restore");
+ if (!force_restore)
+ return err;
+ }
+ }
+ return finalerr;
+}