The UCM stores the mixer settings per use case in configuration files, these files contains information such: - list of use case verbs per card or machine - supported devices per use case verb - use case sink device - use case source device - hardware playback volume control id (per use case verb and device) - hardware playback mute switch id - hardware capture volume control id - hardware capture mute switch id The new ucm data structures are used to store that info so it can be used by pulseaudio. This patch also adds ALSA UCM (Use Case Manager) calls. It checks if UCM is available for each card and if found, scans each UCM property verb, device and modifier for the card. It then then creates mappings for each supported UCM verb and device so that changes to the pulseaudio profile cause corresponding changes to the UCM verb and device for the card. Signed-off-by: Margarita Olaya Cabrera <magi at slimlogic.co.uk> --- src/Makefile.am | 2 +- src/modules/alsa/alsa-ucm.c | 529 +++++++++++++++++++++++++++++++++++++++++++ src/modules/alsa/alsa-ucm.h | 81 +++++++ 3 files changed, 611 insertions(+), 1 deletions(-) create mode 100644 src/modules/alsa/alsa-ucm.c create mode 100644 src/modules/alsa/alsa-ucm.h diff --git a/src/Makefile.am b/src/Makefile.am index 71ad19b..38bc903 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1553,7 +1553,7 @@ module_coreaudio_device_la_LIBADD = $(MODULE_LIBADD) # ALSA -libalsa_util_la_SOURCES = modules/alsa/alsa-util.c modules/alsa/alsa-util.h modules/alsa/alsa-mixer.c modules/alsa/alsa-mixer.h modules/alsa/alsa-sink.c modules/alsa/alsa-sink.h modules/alsa/alsa-source.c modules/alsa/alsa-source.h modules/reserve-wrap.c modules/reserve-wrap.h +libalsa_util_la_SOURCES = modules/alsa/alsa-util.c modules/alsa/alsa-util.h modules/alsa/alsa-mixer.c modules/alsa/alsa-mixer.h modules/alsa/alsa-sink.c modules/alsa/alsa-sink.h modules/alsa/alsa-source.c modules/alsa/alsa-source.h modules/alsa/alsa-ucm.c modules/alsa/alsa-ucm.h modules/reserve-wrap.c modules/reserve-wrap.h libalsa_util_la_LDFLAGS = -avoid-version libalsa_util_la_LIBADD = $(MODULE_LIBADD) $(ASOUNDLIB_LIBS) libalsa_util_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c new file mode 100644 index 0000000..845d7eb --- /dev/null +++ b/src/modules/alsa/alsa-ucm.c @@ -0,0 +1,529 @@ +/*** + This file is part of PulseAudio. + + Copyright 2011 Wolfson Microelectronics PLC + Author Margarita Olaya <magi at slimlogic.co.uk> + + PulseAudio 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. + + PulseAudio 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 Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. + +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <limits.h> +#include <asoundlib.h> + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include <valgrind/memcheck.h> +#endif + +#include <pulse/sample.h> +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/i18n.h> +#include <pulse/utf8.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/atomic.h> +#include <pulsecore/core-error.h> +#include <pulsecore/once.h> +#include <pulsecore/thread.h> +#include <pulsecore/conf-parser.h> +#include <pulsecore/strbuf.h> + +#include "alsa-mixer.h" +#include "alsa-util.h" +#include "alsa-ucm.h" + +struct ucm_items { + const char *id; + const char *property; +}; + +struct ucm_info { + const char *id; + int priority; + pa_alsa_direction_t direction; + int channels; +}; + +static struct ucm_items item[] = { + {"PlaybackPCM", PA_PROP_UCM_SINK}, + {"CapturePCM", PA_PROP_UCM_SOURCE}, + {"PlaybackVolume", PA_PROP_UCM_PLAYBACK_VOLUME}, + {"PlaybackSwitch", PA_PROP_UCM_PLAYBACK_SWITCH}, + {"CaptureVolume", PA_PROP_UCM_CAPTURE_VOLUME}, + {"CaptureSwitch", PA_PROP_UCM_CAPTURE_SWITCH}, + {"TQ", PA_PROP_UCM_QOS}, + {NULL, NULL}, +}; + +/* UCM device info - this should eventually be part of policy manangement */ +static struct ucm_info dev_info[] = { + {SND_USE_CASE_DEV_SPEAKER, 100, PA_ALSA_DIRECTION_OUTPUT, 2}, + {SND_USE_CASE_DEV_LINE, 100, PA_ALSA_DIRECTION_ANY, 2}, + {SND_USE_CASE_DEV_HEADPHONES, 100, PA_ALSA_DIRECTION_OUTPUT, 2}, + {SND_USE_CASE_DEV_HEADSET, 300, PA_ALSA_DIRECTION_ANY, 2}, + {SND_USE_CASE_DEV_HANDSET, 200, PA_ALSA_DIRECTION_ANY, 2}, + {SND_USE_CASE_DEV_BLUETOOTH, 400, PA_ALSA_DIRECTION_ANY, 1}, + {SND_USE_CASE_DEV_EARPIECE, 100, PA_ALSA_DIRECTION_OUTPUT, 1}, + {SND_USE_CASE_DEV_SPDIF, 100, PA_ALSA_DIRECTION_ANY, 2}, + {SND_USE_CASE_DEV_HDMI, 100, PA_ALSA_DIRECTION_ANY, 8}, + {SND_USE_CASE_DEV_NONE, 100, PA_ALSA_DIRECTION_ANY, 2}, + {NULL, 0, PA_ALSA_DIRECTION_ANY, 0}, +}; + +/* UCM profile properties - The verb data is store so it can be used to fill + * the new profiles properties */ + +int ucm_get_property(struct pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr, const char *verb_name) { + const char *value; + int i = 0; + + do { + int err; + + err = snd_use_case_get(uc_mgr, item[i].id, &value); + if (err < 0 ) { + pa_log_info("No %s for verb %s", item[i].id, verb_name); + continue; + } + + pa_log_info("Got %s for verb %s", item[i].id, verb_name); + pa_proplist_sets(verb->proplist, item[i].property, value); + } while (item[++i].id); + + return 0; +}; + +/* Create a property list for this ucm device */ +static int ucm_get_device_property(struct pa_alsa_ucm_device *device, snd_use_case_mgr_t *uc_mgr, const char *device_name) { + const char *value; + char *id; + int i = 0; + + do { + int err; + + id = pa_sprintf_malloc("%s/%s", item[i].id, device_name); + + err = snd_use_case_get(uc_mgr, id, &value); + if (err < 0 ) { + pa_log_info("No %s for device %s", id, device_name); + pa_xfree(id); + continue; + } + + pa_log_info("Got %s for device %s", id, device_name); + pa_xfree(id); + pa_proplist_sets(device->proplist, item[i].property, value); + } while (item[++i].id); + + return 0; +}; + +/* Create a property list for this ucm modifier */ +static int ucm_get_modifier_property(struct pa_alsa_ucm_modifier *modifier, snd_use_case_mgr_t *uc_mgr, const char *modifier_name) { + const char *value; + char *id; + int i = 0; + + do { + int err; + + id = pa_sprintf_malloc("%s/%s", item[i].id, modifier_name); + + err = snd_use_case_get(uc_mgr, id, &value); + if (err < 0 ) { + pa_log_info("No %s for modifier %s", id, modifier_name); + pa_xfree(id); + continue; + } + + pa_log_info("Got %s for modifier %s", id, modifier_name); + pa_xfree(id); + pa_proplist_sets(modifier->proplist, item[i].property, value); + } while (item[++i].id); + + return 0; +}; + +/* Create a list of devices for this verb */ +static int ucm_get_devices(struct pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) { + const char **dev_list; + int num_dev, i; + + num_dev = snd_use_case_get_list(uc_mgr, "_devices", &dev_list); + if (num_dev < 0) + return num_dev; + + for (i = 0; i < num_dev; i += 2) { + pa_alsa_ucm_device *d; + d = pa_xnew0(pa_alsa_ucm_device, 1); + d->proplist = pa_proplist_new(); + pa_proplist_sets(d->proplist, PA_PROP_UCM_NAME, dev_list[i]); + PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d); + } + + return 0; +}; + +static int ucm_get_modifiers(struct pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) { + const char **mod_list; + int num_mod, i; + + num_mod = snd_use_case_get_list(uc_mgr, "_modifiers", &mod_list); + if (num_mod < 0) + return num_mod; + + for (i = 0; i < num_mod; i += 2) { + pa_alsa_ucm_modifier *m; + m = pa_xnew0(pa_alsa_ucm_modifier, 1); + m->proplist = pa_proplist_new(); + pa_proplist_sets(m->proplist, PA_PROP_UCM_NAME, mod_list[i]); + PA_LLIST_PREPEND(pa_alsa_ucm_modifier, verb->modifiers, m); + } + + return 0; +}; + +int ucm_get_properties(struct pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr, const char *verb_name) { + struct pa_alsa_ucm_device *d; + struct pa_alsa_ucm_modifier *mod; + int err; + + err = snd_use_case_set(uc_mgr, "_verb", verb_name); + if (err < 0) + return err; + + err = ucm_get_devices(verb, uc_mgr); + if (err < 0) + pa_log("No UCM devices for verb %s", verb_name); + + err = ucm_get_modifiers(verb, uc_mgr); + if (err < 0) + pa_log("No UCM modifiers for verb %s", verb_name); + + /* Verb properties */ + ucm_get_property(verb, uc_mgr, verb_name); + + PA_LLIST_FOREACH(d, verb->devices) { + const char *dev_name = pa_proplist_gets(d->proplist, PA_PROP_UCM_NAME); + + /* Devices properties */ + ucm_get_device_property(d, uc_mgr, dev_name); + } + + PA_LLIST_FOREACH(mod, verb->modifiers) { + const char *mod_name = pa_proplist_gets(mod->proplist, PA_PROP_UCM_NAME); + + /* Modifier properties */ + ucm_get_modifier_property(mod, uc_mgr, mod_name); + } + + return 0; +} +/* Change UCM verb and device to match selected card profile */ +int ucm_set_profile(struct pa_alsa_ucm_config *ucm, char *profile_name, struct profile_data *d) +{ + char *new_verb_name, *new_device_name, *old_verb_name, *old_device_name, *tmp; + int ret = 0; + + new_device_name = strchr(profile_name, ':') + 2; + if (!new_device_name) { + pa_log("no new device found for %s", profile_name); + return -1; + } + + old_device_name = strchr(d->profile->name, ':') + 2; + if (!old_device_name) { + pa_log("no current device found for %s", d->profile->name); + return -1; + } + + new_verb_name = pa_xstrdup(profile_name); + tmp = strchr(new_verb_name, ':'); + if (!tmp) { + pa_log("no new verb found for %s", profile_name); + pa_xfree(new_verb_name); + return -1; + } + *tmp = 0; + + old_verb_name = pa_xstrdup(d->profile->name); + tmp = strchr(old_verb_name, ':'); + if (!tmp) { + pa_log("no new verb found for %s", d->profile->name); + pa_xfree(new_verb_name); + pa_xfree(old_verb_name); + return -1; + } + *tmp = 0; + + pa_log("set ucm: old verb %s device %s", old_verb_name, old_device_name); + pa_log("set ucm: new verb %s device %s", new_verb_name, new_device_name); + + /* do we need to change the verb */ + if (strcmp(new_verb_name, old_verb_name) == 0) { + /* just change the device only */ + tmp = pa_sprintf_malloc("_swdev/%s", old_device_name); + if ((snd_use_case_set(ucm->ucm_mgr, tmp, new_device_name)) < 0) { + pa_log("failed to switch device %s %s", tmp, new_device_name); + ret = -1; + } + pa_xfree(tmp); + } else { + /* change verb and device */ + if ((snd_use_case_set(ucm->ucm_mgr, "_verb", new_verb_name)) < 0) { + pa_log("failed to set verb %s", new_verb_name); + ret = -1; + } + if (snd_use_case_set(ucm->ucm_mgr, "_enadev", new_device_name) < 0) { + pa_log("failed to set device %s", new_device_name); + ret = -1; + } + } + + pa_xfree(new_verb_name); + pa_xfree(old_verb_name); + return ret; +} + +static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) +{ + switch (m->direction) { + case PA_ALSA_DIRECTION_ANY: + pa_idxset_put(p->output_mappings, m, NULL); + pa_idxset_put(p->input_mappings, m, NULL); + break; + case PA_ALSA_DIRECTION_OUTPUT: + pa_idxset_put(p->output_mappings, m, NULL); + break; + case PA_ALSA_DIRECTION_INPUT: + pa_idxset_put(p->input_mappings, m, NULL); + break; + } + p->priority += m->priority * 100; +} + +static pa_alsa_profile *ucm_new_profile(pa_alsa_profile_set *ps, const char *verb_name, const char *dev_name) +{ + pa_alsa_profile *p; + char *profile_name; + + if (dev_name) + profile_name = pa_sprintf_malloc("%s: %s", verb_name, dev_name); + else + profile_name = pa_sprintf_malloc("%s:", verb_name); + + if (pa_hashmap_get(ps->profiles, verb_name)) { + pa_xfree(profile_name); + return NULL; + } + + p = pa_xnew0(pa_alsa_profile, 1); + p->profile_set = ps; + p->name = profile_name; + + p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + ps->probed = TRUE; + p->supported = 1; + pa_hashmap_put(ps->profiles, p->name, p); + return p; +} + +static int ucm_create_mapping(pa_alsa_profile_set *ps, struct pa_alsa_profile *p, struct pa_alsa_ucm_device *device, const char *verb_name, const char *device_name, char *strings) +{ + pa_alsa_mapping *m; + char *mapping_name; + int i = 0; + + if (device_name) + mapping_name = pa_sprintf_malloc("Mapping %s: %s", verb_name, device_name); + else + mapping_name = pa_sprintf_malloc("Mapping %s", verb_name); + + m = mapping_get(ps, mapping_name); + if (!m) { + pa_log("no mapping for %s", mapping_name); + pa_xfree(mapping_name); + return -1; + } + pa_log_info("ucm mapping: %s dev %s", mapping_name, strings); + + m->supported = TRUE; + m->channel_map.map[0] = PA_CHANNEL_POSITION_LEFT; + m->channel_map.map[1] = PA_CHANNEL_POSITION_RIGHT; + m->device_strings = pa_split_spaces_strv(strings); + pa_xfree(mapping_name); + + if (!device_name) + goto not_found; + do { + if (strcmp(dev_info[i].id, device_name) == 0) + goto found; + } while (dev_info[++i].id); + +not_found: + /* use default values */ + m->priority = 100; + m->direction = PA_ALSA_DIRECTION_ANY; + m->channel_map.channels = 2; + ucm_add_mapping(p, m); + return 0; + +found: + m->priority = dev_info[i].priority; + m->direction = dev_info[i].direction; + m->channel_map.channels = dev_info[i].channels; + ucm_add_mapping(p, m); + return 0; +} + +static int ucm_create_profile(pa_alsa_profile_set *ps, struct pa_alsa_ucm_verb *verb, + const char *verb_name, const char *verb_sink, const char *verb_source) { + + struct pa_alsa_profile *p; + struct pa_alsa_ucm_device *dev; + char *dev_strings; + int num_devices = 0; + + pa_assert(ps); + + /* Add a mapping for each verb modifier for this profile if the sink/source is different to the verb */ + PA_LLIST_FOREACH(dev, verb->devices) { + const char *dev_name, *sink, *source; + + dev_name = pa_proplist_gets(dev->proplist, PA_PROP_UCM_NAME); + + /* if no default sink is set use hw:0 */ + sink = pa_proplist_gets(dev->proplist, PA_PROP_UCM_SINK); + if (sink == NULL) + sink = "hw:0"; + + /* if no default sink is set use hw:0 */ + source = pa_proplist_gets(dev->proplist, PA_PROP_UCM_SOURCE); + if (source == NULL) + source = "hw:0"; + + dev_strings = pa_sprintf_malloc("%s %s", sink, source); + p = ucm_new_profile(ps, verb_name, dev_name); + ucm_create_mapping(ps, p, dev, verb_name, dev_name, dev_strings); + pa_xfree(dev_strings); + pa_alsa_profile_dump(p); + num_devices++; + } + + if (num_devices) + return 0; + + /* Create a default mapping for each verb/profile */ + dev_strings = pa_sprintf_malloc("%s %s", verb_sink, verb_source); + p = ucm_new_profile(ps, verb_name, NULL); + ucm_create_mapping(ps, p, dev, verb_name, NULL, dev_strings); + pa_xfree(dev); + + return 0; +} + +pa_alsa_profile_set* add_ucm_profile_set(struct pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) { + struct pa_alsa_ucm_verb *verb; + pa_alsa_profile_set *ps; + pa_alsa_profile *p; + pa_alsa_mapping *m; + void *state; + + ps = pa_xnew0(pa_alsa_profile_set, 1); + ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + /* create a profile for each verb */ + PA_LLIST_FOREACH(verb, ucm->verbs) { + const char *sink, *source, *verb_name; + char *dev; + + verb_name = pa_proplist_gets(verb->proplist, PA_PROP_UCM_NAME); + if (verb_name == NULL) { + pa_log("verb with no name"); + continue; + } + + /* if no default sink is set use hw:0 */ + sink = pa_proplist_gets(verb->proplist, PA_PROP_UCM_SINK); + if (sink == NULL) + sink = "hw:0"; + + /* if no default sink is set use hw:0 */ + source = pa_proplist_gets(verb->proplist, PA_PROP_UCM_SOURCE); + if (source == NULL) + source = "hw:0"; + + dev = pa_sprintf_malloc("%s %s", sink, source); + ucm_create_profile(ps, verb, verb_name, sink, source); + pa_xfree(dev); + } + + PA_HASHMAP_FOREACH(m, ps->mappings, state) + if (mapping_verify(m, default_channel_map) < 0) + goto fail; + + PA_HASHMAP_FOREACH(p, ps->profiles, state) + if (profile_verify(p) < 0) + goto fail; + + return ps; + +fail: + pa_log("failed to add UCM mappings"); + pa_alsa_profile_set_free(ps); + return NULL; +} + +void free_ucm(struct pa_alsa_ucm_config *ucm) +{ + struct pa_alsa_ucm_device *di, *dn; + struct pa_alsa_ucm_modifier *mi, *mn; + struct pa_alsa_ucm_verb *verb, *vi, *vn; + + verb = ucm->verbs; + + PA_LLIST_FOREACH_SAFE(di, dn, verb->devices) { + PA_LLIST_REMOVE(pa_alsa_ucm_device, verb->devices, di); + pa_proplist_free(di->proplist); + pa_xfree(di); + } + + PA_LLIST_FOREACH_SAFE(mi, mn, verb->modifiers) { + PA_LLIST_REMOVE(pa_alsa_ucm_modifier, verb->modifiers, mi); + pa_proplist_free(mi->proplist); + pa_xfree(mi); + } + + PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) { + PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi); + pa_proplist_free(vi->proplist); + pa_xfree(vi); + } +} diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h new file mode 100644 index 0000000..97fcfca --- /dev/null +++ b/src/modules/alsa/alsa-ucm.h @@ -0,0 +1,81 @@ +#ifndef foopulseucmhfoo +#define foopulseucmhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2011 Wolfson Microelectronics PLC + Author Margarita Olaya <magi at slimlogic.co.uk> + + PulseAudio 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. + + PulseAudio 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 Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <asoundlib.h> +#include <use-case.h> + +typedef struct pa_alsa_ucm_verb pa_alsa_ucm_verb; +typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier; +typedef struct pa_alsa_ucm_device pa_alsa_ucm_device; +typedef struct pa_alsa_ucm_config pa_alsa_ucm_config; + +int ucm_set_profile(struct pa_alsa_ucm_config *ucm, char *profile_name, struct profile_data *d); +void free_ucm(struct pa_alsa_ucm_config *ucm); +pa_alsa_profile_set* add_ucm_profile_set(struct pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map); +int ucm_get_property(struct pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr, const char *verb_name); +int ucm_get_properties(struct pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr, const char *verb_name); + +typedef enum pa_alsa_ucm_mapping_type { + PA_ALSA_UCM_MAPPING_DEVICE, + PA_ALSA_UCM_MAPPING_MODIFIER +} pa_alsa_ucm_mapping_type_t; + +/* UCM - Use Case Manager is available on some audio cards */ + +typedef enum pa_alsa_ucm_status { + PA_ALSA_UCM_DISABLED = 0, + PA_ALSA_UCM_ENABLED, +} pa_alsa_ucm_status_t; + +struct pa_alsa_ucm_device { + PA_LLIST_FIELDS(pa_alsa_ucm_device); + pa_proplist *proplist; + pa_alsa_ucm_status_t status; +}; + +struct pa_alsa_ucm_modifier { + PA_LLIST_FIELDS(pa_alsa_ucm_modifier); + pa_proplist *proplist; + pa_alsa_ucm_status_t status; +}; + +struct pa_alsa_ucm_verb { + PA_LLIST_FIELDS(pa_alsa_ucm_verb); + pa_proplist *proplist; + pa_alsa_ucm_status_t status; + PA_LLIST_HEAD(pa_alsa_ucm_device, devices); + PA_LLIST_HEAD(pa_alsa_ucm_modifier, modifiers); +}; + +struct pa_alsa_ucm_config { + snd_use_case_mgr_t *ucm_mgr; + const char *verb_ini; + const char *verb_new; + pa_alsa_ucm_status_t status; + + PA_LLIST_HEAD(pa_alsa_ucm_verb, verbs); +}; + +#endif -- 1.7.0.4