/** * \file control/setup.c * \brief Routines to setup control primitives from configuration * \author Abramo Bagnara * \author Jaroslav Kysela * \date 2001 * * Routines to setup control primitives from configuration */ /* * Control Interface - routines for setup from configuration * Copyright (c) 2001 by Abramo Bagnara * Jaroslav Kysela * * * 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 #include #include #include #include #include #include "local.h" #ifndef DOC_HIDDEN typedef struct { unsigned int lock: 1; unsigned int preserve: 1; snd_ctl_elem_id_t *id; snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *val; snd_ctl_elem_value_t *mask; snd_ctl_elem_value_t *old; struct list_head list; } snd_sctl_elem_t; struct _snd_sctl { int mode; snd_ctl_t *ctl; struct list_head elems; }; #endif /* DOC_HIDDEN */ static int free_elems(snd_sctl_t *h) { int err = 0; while (!list_empty(&h->elems)) { snd_sctl_elem_t *elem = list_entry(h->elems.next, snd_sctl_elem_t, list); snd_ctl_elem_id_free(elem->id); snd_ctl_elem_info_free(elem->info); snd_ctl_elem_value_free(elem->val); snd_ctl_elem_value_free(elem->mask); snd_ctl_elem_value_free(elem->old); list_del(&elem->list); free(elem); } if ((h->mode & SND_SCTL_NOFREE) == 0) err = snd_ctl_close(h->ctl); free(h); return err; } /** * \brief Install given values to control elements * \param h Setup control handle * \result zero if success, otherwise a negative error code */ int snd_sctl_install(snd_sctl_t *h) { struct list_head *pos; int err; unsigned int k; assert(h); list_for_each(pos, &h->elems) { snd_sctl_elem_t *elem = list_entry(pos, snd_sctl_elem_t, list); unsigned int count; snd_ctl_elem_type_t type; if (elem->lock) { err = snd_ctl_elem_lock(h->ctl, elem->id); if (err < 0) { SNDERR("Cannot lock ctl elem"); return err; } } err = snd_ctl_elem_read(h->ctl, elem->old); if (err < 0) { SNDERR("Cannot read ctl elem"); return err; } count = snd_ctl_elem_info_get_count(elem->info); type = snd_ctl_elem_info_get_type(elem->info); switch (type) { case SND_CTL_ELEM_TYPE_BOOLEAN: for (k = 0; k < count; ++k) { int old, val, mask; old = snd_ctl_elem_value_get_boolean(elem->old, k); mask = snd_ctl_elem_value_get_boolean(elem->mask, k); old &= ~mask; if (old) { val = snd_ctl_elem_value_get_boolean(elem->val, k); val |= old; snd_ctl_elem_value_set_boolean(elem->val, k, val); } } break; case SND_CTL_ELEM_TYPE_INTEGER: for (k = 0; k < count; ++k) { long old, val, mask; old = snd_ctl_elem_value_get_integer(elem->old, k); mask = snd_ctl_elem_value_get_integer(elem->mask, k); old &= ~mask; if (old) { val = snd_ctl_elem_value_get_integer(elem->val, k); val |= old; snd_ctl_elem_value_set_integer(elem->val, k, val); } } break; case SND_CTL_ELEM_TYPE_ENUMERATED: for (k = 0; k < count; ++k) { unsigned int old, val, mask; old = snd_ctl_elem_value_get_enumerated(elem->old, k); mask = snd_ctl_elem_value_get_enumerated(elem->mask, k); old &= ~mask; if (old) { val = snd_ctl_elem_value_get_enumerated(elem->val, k); val |= old; snd_ctl_elem_value_set_enumerated(elem->val, k, val); } } break; case SND_CTL_ELEM_TYPE_IEC958: count = sizeof(snd_aes_iec958_t); /* Fall through */ case SND_CTL_ELEM_TYPE_BYTES: for (k = 0; k < count; ++k) { unsigned char old, val, mask; old = snd_ctl_elem_value_get_byte(elem->old, k); mask = snd_ctl_elem_value_get_byte(elem->mask, k); old &= ~mask; if (old) { val = snd_ctl_elem_value_get_byte(elem->val, k); val |= old; snd_ctl_elem_value_set_byte(elem->val, k, val); } } break; default: assert(0); break; } err = snd_ctl_elem_write(h->ctl, elem->val); if (err < 0) { SNDERR("Cannot write ctl elem"); return err; } } return 0; } /** * \brief Remove (restore) previous values from control elements * \param h Setup control handle * \result zero if success, otherwise a negative error code */ int snd_sctl_remove(snd_sctl_t *h) { struct list_head *pos; int err; assert(h); list_for_each(pos, &h->elems) { snd_sctl_elem_t *elem = list_entry(pos, snd_sctl_elem_t, list); if (elem->lock) { err = snd_ctl_elem_unlock(h->ctl, elem->id); if (err < 0) { SNDERR("Cannot unlock ctl elem"); return err; } } /* Only restore the old value if it differs from the requested * value, because if it has changed restoring the old value * overrides the change. Take for example, a voice modem with * a .conf that sets preserve off-hook. Start playback (on-hook * to off-hook), start record (off-hook to off-hook), stop * playback (off-hook to restore on-hook), stop record (on-hook * to restore off-hook), Clearly you don't want to leave the * modem "on the phone" now that there isn't any playback or * recording active. */ if (elem->preserve && snd_ctl_elem_value_compare(elem->val, elem->old)) { err = snd_ctl_elem_write(h->ctl, elem->old); if (err < 0) { SNDERR("Cannot restore ctl elem"); return err; } } } return 0; } static int snd_config_get_ctl_elem_enumerated(snd_config_t *n, snd_ctl_t *ctl, snd_ctl_elem_info_t *info) { const char *str; long val; 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_STRING: snd_config_get_string(n, &str); break; 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(ctl, info); if (err < 0) { SNDERR("Cannot obtain info for CTL elem"); return err; } if (strcmp(str, snd_ctl_elem_info_get_item_name(info)) == 0) return idx; } return -1; } static int snd_config_get_ctl_elem_value(snd_config_t *conf, snd_ctl_t *ctl, snd_ctl_elem_value_t *val, snd_ctl_elem_value_t *mask, snd_ctl_elem_info_t *info) { int err; snd_config_iterator_t i, next; snd_ctl_elem_id_t id = {0}; snd_ctl_elem_type_t type; unsigned int count; long v; long idx; snd_ctl_elem_value_get_id(val, &id); count = snd_ctl_elem_info_get_count(info); type = snd_ctl_elem_info_get_type(info); if (count == 1) { switch (type) { case SND_CTL_ELEM_TYPE_BOOLEAN: v = snd_config_get_bool(conf); if (v >= 0) { snd_ctl_elem_value_set_boolean(val, 0, v); if (mask) snd_ctl_elem_value_set_boolean(mask, 0, 1); return 0; } break; case SND_CTL_ELEM_TYPE_INTEGER: err = snd_config_get_integer(conf, &v); if (err == 0) { snd_ctl_elem_value_set_integer(val, 0, v); if (mask) snd_ctl_elem_value_set_integer(mask, 0, ~0L); return 0; } break; case SND_CTL_ELEM_TYPE_ENUMERATED: v = snd_config_get_ctl_elem_enumerated(conf, ctl, info); if (v >= 0) { snd_ctl_elem_value_set_enumerated(val, 0, v); if (mask) snd_ctl_elem_value_set_enumerated(mask, 0, ~0); return 0; } break; case SND_CTL_ELEM_TYPE_BYTES: case SND_CTL_ELEM_TYPE_IEC958: break; default: SNDERR("Unknown control type: %d", type); return -EINVAL; } } switch (type) { case SND_CTL_ELEM_TYPE_IEC958: count = sizeof(snd_aes_iec958_t); /* Fall through */ case SND_CTL_ELEM_TYPE_BYTES: { const char *buf; err = snd_config_get_string(conf, &buf); if (err >= 0) { int c1 = 0; unsigned int len = strlen(buf); unsigned int idx = 0; if (len % 2 != 0 || len > count * 2) { _bad_content: SNDERR("bad value content\n"); return -EINVAL; } while (*buf) { int c = *buf++; if (c >= '0' && c <= '9') c -= '0'; else if (c >= 'a' && c <= 'f') c = c - 'a' + 10; else if (c >= 'A' && c <= 'F') c = c - 'A' + 10; else { goto _bad_content; } if (idx % 2 == 1) { snd_ctl_elem_value_set_byte(val, idx / 2, c1 << 4 | c); if (mask) snd_ctl_elem_value_set_byte(mask, idx / 2, 0xff); } else c1 = c; idx++; } return 0; } } default: break; } if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) { SNDERR("bad value type"); return -EINVAL; } 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; err = safe_strtol(id, &idx); if (err < 0 || idx < 0 || (unsigned int) idx >= count) { SNDERR("bad value index"); return -EINVAL; } switch (type) { case SND_CTL_ELEM_TYPE_BOOLEAN: v = snd_config_get_bool(n); if (v < 0) goto _bad_content; snd_ctl_elem_value_set_boolean(val, idx, v); if (mask) snd_ctl_elem_value_set_boolean(mask, idx, 1); break; case SND_CTL_ELEM_TYPE_INTEGER: err = snd_config_get_integer(n, &v); if (err < 0) goto _bad_content; snd_ctl_elem_value_set_integer(val, idx, v); if (mask) snd_ctl_elem_value_set_integer(mask, idx, ~0L); break; case SND_CTL_ELEM_TYPE_ENUMERATED: v = snd_config_get_ctl_elem_enumerated(n, ctl, info); if (v < 0) goto _bad_content; snd_ctl_elem_value_set_enumerated(val, idx, v); if (mask) snd_ctl_elem_value_set_enumerated(mask, idx, ~0); break; case SND_CTL_ELEM_TYPE_BYTES: case SND_CTL_ELEM_TYPE_IEC958: err = snd_config_get_integer(n, &v); if (err < 0 || v < 0 || v > 255) goto _bad_content; snd_ctl_elem_value_set_byte(val, idx, v); if (mask) snd_ctl_elem_value_set_byte(mask, idx, 0xff); break; default: break; } } return 0; } static int add_elem(snd_sctl_t *h, snd_config_t *_conf, snd_config_t *private_data, int *quit) { snd_config_t *conf; snd_config_iterator_t i, next; int iface = SND_CTL_ELEM_IFACE_MIXER; const char *name = NULL; long index = 0; long device = -1; long subdevice = -1; int lock = 0; int preserve = 0; int optional = 0; int skip_rest = 0; snd_config_t *value = NULL, *mask = NULL; snd_sctl_elem_t *elem = NULL; int err; err = snd_config_expand(_conf, _conf, NULL, private_data, &conf); if (err < 0) return err; 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, "comment") == 0) continue; if (strcmp(id, "iface") == 0 || strcmp(id, "interface") == 0) { const char *ptr; if ((err = snd_config_get_string(n, &ptr)) < 0) { SNDERR("field %s is not a string", id); goto _err; } if ((err = snd_config_get_ctl_iface_ascii(ptr)) < 0) { SNDERR("Invalid value for '%s'", id); goto _err; } iface = err; continue; } if (strcmp(id, "name") == 0) { if ((err = snd_config_get_string(n, &name)) < 0) { SNDERR("field %s is not a string", id); goto _err; } continue; } if (strcmp(id, "index") == 0) { if ((err = snd_config_get_integer(n, &index)) < 0) { SNDERR("field %s is not an integer", id); goto _err; } continue; } if (strcmp(id, "device") == 0) { if ((err = snd_config_get_integer(n, &device)) < 0) { SNDERR("field %s is not an integer", id); goto _err; } continue; } if (strcmp(id, "subdevice") == 0) { if ((err = snd_config_get_integer(n, &subdevice)) < 0) { SNDERR("field %s is not an integer", id); goto _err; } continue; } if (strcmp(id, "lock") == 0) { err = snd_config_get_bool(n); if (err < 0) goto _err; lock = err; continue; } if (strcmp(id, "preserve") == 0) { err = snd_config_get_bool(n); if (err < 0) goto _err; preserve = err; continue; } if (strcmp(id, "value") == 0) { value = n; continue; } if (strcmp(id, "mask") == 0) { mask = n; continue; } if (strcmp(id, "optional") == 0) { err = snd_config_get_bool(n); if (err < 0) goto _err; optional = err; continue; } if (strcmp(id, "skip_rest") == 0) { err = snd_config_get_bool(n); if (err < 0) goto _err; skip_rest = err; continue; } SNDERR("Unknown field %s", id); return -EINVAL; } if (name == NULL) { SNDERR("Missing control name"); err = -EINVAL; goto _err; } if (value == NULL) { SNDERR("Missing control value"); err = -EINVAL; goto _err; } if (device < 0) device = 0; if (subdevice < 0) subdevice = 0; elem = calloc(1, sizeof(*elem)); if (!elem) return -ENOMEM; err = snd_ctl_elem_id_malloc(&elem->id); if (err < 0) goto _err; err = snd_ctl_elem_info_malloc(&elem->info); if (err < 0) goto _err; err = snd_ctl_elem_value_malloc(&elem->val); if (err < 0) goto _err; err = snd_ctl_elem_value_malloc(&elem->mask); if (err < 0) goto _err; err = snd_ctl_elem_value_malloc(&elem->old); if (err < 0) goto _err; elem->lock = lock; elem->preserve = preserve; snd_ctl_elem_id_set_interface(elem->id, iface); snd_ctl_elem_id_set_name(elem->id, name); snd_ctl_elem_id_set_index(elem->id, index); snd_ctl_elem_id_set_device(elem->id, device); snd_ctl_elem_id_set_subdevice(elem->id, subdevice); snd_ctl_elem_info_set_id(elem->info, elem->id); err = snd_ctl_elem_info(h->ctl, elem->info); if (err < 0) { if (! optional) SNDERR("Cannot obtain info for CTL elem (%s,'%s',%li,%li,%li): %s", snd_ctl_elem_iface_name(iface), name, index, device, subdevice, snd_strerror(err)); goto _err; } else { if (skip_rest) *quit = 1; } snd_ctl_elem_value_set_id(elem->val, elem->id); snd_ctl_elem_value_set_id(elem->old, elem->id); if (mask) { err = snd_config_get_ctl_elem_value(value, h->ctl, elem->val, NULL, elem->info); if (err < 0) goto _err; err = snd_config_get_ctl_elem_value(mask, h->ctl, elem->mask, NULL, elem->info); if (err < 0) goto _err; } else { err = snd_config_get_ctl_elem_value(value, h->ctl, elem->val, elem->mask, elem->info); if (err < 0) goto _err; } err = snd_config_get_ctl_elem_value(value, h->ctl, elem->val, elem->mask, elem->info); if (err < 0) goto _err; list_add_tail(&elem->list, &h->elems); _err: if (err < 0 && elem) { if (elem->id) snd_ctl_elem_id_free(elem->id); if (elem->info) snd_ctl_elem_info_free(elem->info); if (elem->val) snd_ctl_elem_value_free(elem->val); if (elem->mask) snd_ctl_elem_value_free(elem->mask); if (elem->old) snd_ctl_elem_value_free(elem->old); free(elem); if (err != -ENOMEM && optional) err = 0; /* ignore the error */ } if (conf) snd_config_delete(conf); return err; } /** * \brief Build setup control handle * \param sctl Result - setup control handle * \param handle Master control handle * \param conf Setup configuration * \param private_data Private data for runtime evaluation * \param mode Build mode - SND_SCTL_xxxx * \result zero if success, otherwise a negative error code */ int snd_sctl_build(snd_sctl_t **sctl, snd_ctl_t *handle, snd_config_t *conf, snd_config_t *private_data, int mode) { snd_sctl_t *h; snd_config_iterator_t i, next; int err, quit = 0; assert(sctl); assert(handle); assert(conf); *sctl = NULL; if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) return -EINVAL; h = calloc(1, sizeof(*h)); if (!h) { if (mode & SND_SCTL_NOFREE) return -ENOMEM; snd_ctl_close(handle); return -ENOMEM; } h->mode = mode; h->ctl = handle; INIT_LIST_HEAD(&h->elems); snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); err = add_elem(h, n, private_data, &quit); if (err < 0) { free_elems(h); return err; } if (quit) break; } *sctl = h; return 0; } /** * \brief Free setup control handle * \param sctl Setup control handle * \result zero if success, otherwise a negative error code */ int snd_sctl_free(snd_sctl_t *sctl) { assert(sctl); return free_elems(sctl); }