| /** | |
| * \file mixer/simple_abst.c | |
| * \brief Mixer Simple Element Class Interface - Module Abstraction | |
| * \author Jaroslav Kysela <perex@perex.cz> | |
| * \date 2005 | |
| * | |
| * Mixer simple element class interface. | |
| */ | |
| /* | |
| * Mixer Interface - simple controls - abstraction module | |
| * Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz> | |
| * | |
| * | |
| * This library is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as | |
| * published by the Free Software Foundation; either version 2.1 of | |
| * the License, or (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| #include "mixer_local.h" | |
| #include "mixer_simple.h" | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <string.h> | |
| #include <fcntl.h> | |
| #include <sys/ioctl.h> | |
| #include <math.h> | |
| #include <dlfcn.h> | |
| #ifndef DOC_HIDDEN | |
| #define SO_PATH "smixer" | |
| typedef struct _class_priv { | |
| char *device; | |
| snd_ctl_t *ctl; | |
| snd_hctl_t *hctl; | |
| int attach_flag; | |
| snd_ctl_card_info_t *info; | |
| void *dlhandle; | |
| void *private_data; | |
| void (*private_free)(snd_mixer_class_t *class); | |
| } class_priv_t; | |
| typedef int (*snd_mixer_sbasic_init_t)(snd_mixer_class_t *class); | |
| typedef int (*snd_mixer_sfbasic_init_t)(snd_mixer_class_t *class, | |
| snd_mixer_t *mixer, | |
| const char *device); | |
| #endif /* !DOC_HIDDEN */ | |
| static int try_open(snd_mixer_class_t *class, const char *lib) | |
| { | |
| class_priv_t *priv = snd_mixer_class_get_private(class); | |
| snd_mixer_event_t event_func; | |
| snd_mixer_sbasic_init_t init_func = NULL; | |
| char *xlib, *path, errbuf[256]; | |
| void *h; | |
| int err = 0; | |
| if (!lib) | |
| return -ENXIO; | |
| path = getenv("ALSA_MIXER_SIMPLE_MODULES"); | |
| if (!path) | |
| path = SO_PATH; | |
| xlib = malloc(strlen(lib) + strlen(path) + 1 + 1); | |
| if (xlib == NULL) | |
| return -ENOMEM; | |
| strcpy(xlib, path); | |
| strcat(xlib, "/"); | |
| strcat(xlib, lib); | |
| h = INTERNAL(snd_dlopen)(xlib, RTLD_NOW, errbuf, sizeof(errbuf)); | |
| if (h == NULL) { | |
| SNDERR("Unable to open library '%s' (%s)", xlib, errbuf); | |
| free(xlib); | |
| return -ENXIO; | |
| } | |
| priv->dlhandle = h; | |
| event_func = snd_dlsym(h, "alsa_mixer_simple_event", NULL); | |
| if (event_func == NULL) { | |
| SNDERR("Symbol 'alsa_mixer_simple_event' was not found in '%s'", xlib); | |
| err = -ENXIO; | |
| } | |
| if (err == 0) { | |
| init_func = snd_dlsym(h, "alsa_mixer_simple_init", NULL); | |
| if (init_func == NULL) { | |
| SNDERR("Symbol 'alsa_mixer_simple_init' was not found in '%s'", xlib); | |
| err = -ENXIO; | |
| } | |
| } | |
| free(xlib); | |
| err = err == 0 ? init_func(class) : err; | |
| if (err < 0) | |
| return err; | |
| snd_mixer_class_set_event(class, event_func); | |
| return 1; | |
| } | |
| static int try_open_full(snd_mixer_class_t *class, snd_mixer_t *mixer, | |
| const char *lib, const char *device) | |
| { | |
| class_priv_t *priv = snd_mixer_class_get_private(class); | |
| snd_mixer_event_t event_func; | |
| snd_mixer_sfbasic_init_t init_func = NULL; | |
| char *xlib, *path, errbuf[256]; | |
| void *h; | |
| int err = 0; | |
| path = getenv("ALSA_MIXER_SIMPLE_MODULES"); | |
| if (!path) | |
| path = SO_PATH; | |
| xlib = malloc(strlen(lib) + strlen(path) + 1 + 1); | |
| if (xlib == NULL) | |
| return -ENOMEM; | |
| strcpy(xlib, path); | |
| strcat(xlib, "/"); | |
| strcat(xlib, lib); | |
| /* note python modules requires RTLD_GLOBAL */ | |
| h = INTERNAL(snd_dlopen)(xlib, RTLD_NOW|RTLD_GLOBAL, errbuf, sizeof(errbuf)); | |
| if (h == NULL) { | |
| SNDERR("Unable to open library '%s'", xlib); | |
| free(xlib); | |
| return -ENXIO; | |
| } | |
| priv->dlhandle = h; | |
| event_func = snd_dlsym(h, "alsa_mixer_simple_event", NULL); | |
| if (event_func == NULL) { | |
| SNDERR("Symbol 'alsa_mixer_simple_event' was not found in '%s'", xlib); | |
| err = -ENXIO; | |
| } | |
| if (err == 0) { | |
| init_func = snd_dlsym(h, "alsa_mixer_simple_finit", NULL); | |
| if (init_func == NULL) { | |
| SNDERR("Symbol 'alsa_mixer_simple_finit' was not found in '%s'", xlib); | |
| err = -ENXIO; | |
| } | |
| } | |
| free(xlib); | |
| err = err == 0 ? init_func(class, mixer, device) : err; | |
| if (err < 0) | |
| return err; | |
| snd_mixer_class_set_event(class, event_func); | |
| return 1; | |
| } | |
| static int match(snd_mixer_class_t *class, const char *lib, const char *searchl) | |
| { | |
| class_priv_t *priv = snd_mixer_class_get_private(class); | |
| const char *components; | |
| if (searchl == NULL) | |
| return try_open(class, lib); | |
| components = snd_ctl_card_info_get_components(priv->info); | |
| while (*components != '\0') { | |
| if (!strncmp(components, searchl, strlen(searchl))) | |
| return try_open(class, lib); | |
| while (*components != ' ' && *components != '\0') | |
| components++; | |
| while (*components == ' ' && *components != '\0') | |
| components++; | |
| } | |
| return 0; | |
| } | |
| static int find_full(snd_mixer_class_t *class, snd_mixer_t *mixer, | |
| snd_config_t *top, const char *device) | |
| { | |
| snd_config_iterator_t i, next; | |
| char *lib; | |
| const char *id; | |
| int err; | |
| snd_config_for_each(i, next, top) { | |
| snd_config_t *n = snd_config_iterator_entry(i); | |
| if (snd_config_get_id(n, &id) < 0) | |
| continue; | |
| if (strcmp(id, "_full")) | |
| continue; | |
| err = snd_config_get_string(n, (const char **)&lib); | |
| if (err < 0) | |
| return err; | |
| err = try_open_full(class, mixer, lib, device); | |
| if (err < 0) | |
| return err; | |
| return 0; | |
| } | |
| return -ENOENT; | |
| } | |
| static int find_module(snd_mixer_class_t *class, snd_config_t *top) | |
| { | |
| snd_config_iterator_t i, next; | |
| snd_config_iterator_t j, jnext; | |
| char *lib, *searchl; | |
| const char *id; | |
| int err; | |
| snd_config_for_each(i, next, top) { | |
| snd_config_t *n = snd_config_iterator_entry(i); | |
| if (snd_config_get_id(n, &id) < 0) | |
| continue; | |
| if (*id == '_') | |
| continue; | |
| searchl = NULL; | |
| lib = NULL; | |
| snd_config_for_each(j, jnext, n) { | |
| snd_config_t *m = snd_config_iterator_entry(j); | |
| if (snd_config_get_id(m, &id) < 0) | |
| continue; | |
| if (!strcmp(id, "searchl")) { | |
| err = snd_config_get_string(m, (const char **)&searchl); | |
| if (err < 0) | |
| return err; | |
| continue; | |
| } | |
| if (!strcmp(id, "lib")) { | |
| err = snd_config_get_string(m, (const char **)&lib); | |
| if (err < 0) | |
| return err; | |
| continue; | |
| } | |
| } | |
| err = match(class, lib, searchl); | |
| if (err == 1) | |
| return 0; | |
| if (err < 0) | |
| return err; | |
| } | |
| return -ENOENT; | |
| } | |
| static void private_free(snd_mixer_class_t *class) | |
| { | |
| class_priv_t *priv = snd_mixer_class_get_private(class); | |
| if (priv->private_free) | |
| priv->private_free(class); | |
| if (priv->dlhandle) | |
| snd_dlclose(priv->dlhandle); | |
| if (priv->info) | |
| snd_ctl_card_info_free(priv->info); | |
| if (priv->hctl) { | |
| if (priv->attach_flag) | |
| snd_mixer_detach_hctl(snd_mixer_class_get_mixer(class), priv->hctl); | |
| snd_hctl_close(priv->hctl); | |
| } else if (priv->ctl) | |
| snd_ctl_close(priv->ctl); | |
| free(priv->device); | |
| free(priv); | |
| } | |
| /** | |
| * \brief Register mixer simple element class - basic abstraction | |
| * \param mixer Mixer handle | |
| * \param options Options container | |
| * \param classp Pointer to returned mixer simple element class handle (or NULL | |
| * \return 0 on success otherwise a negative error code | |
| */ | |
| int snd_mixer_simple_basic_register(snd_mixer_t *mixer, | |
| struct snd_mixer_selem_regopt *options, | |
| snd_mixer_class_t **classp) | |
| { | |
| snd_mixer_class_t *class; | |
| class_priv_t *priv = calloc(1, sizeof(*priv)); | |
| const char *file; | |
| snd_input_t *input; | |
| snd_config_t *top = NULL; | |
| int err; | |
| if (priv == NULL) | |
| return -ENOMEM; | |
| if (options->device == NULL) { | |
| free(priv); | |
| return -EINVAL; | |
| } | |
| if (snd_mixer_class_malloc(&class)) { | |
| free(priv); | |
| return -ENOMEM; | |
| } | |
| priv->device = strdup(options->device); | |
| if (priv->device == NULL) { | |
| free(priv); | |
| snd_mixer_class_free(class); | |
| return -ENOMEM; | |
| } | |
| snd_mixer_class_set_compare(class, snd_mixer_selem_compare); | |
| snd_mixer_class_set_private(class, priv); | |
| snd_mixer_class_set_private_free(class, private_free); | |
| file = getenv("ALSA_MIXER_SIMPLE"); | |
| if (!file) { | |
| const char *topdir = snd_config_topdir(); | |
| char *s = alloca(strlen(topdir) + strlen("smixer.conf") + 2); | |
| sprintf(s, "%s/smixer.conf", topdir); | |
| file = s; | |
| } | |
| err = snd_config_top(&top); | |
| if (err >= 0) { | |
| err = snd_input_stdio_open(&input, file, "r"); | |
| if (err < 0) { | |
| SNDERR("unable to open simple mixer configuration file '%s'", file); | |
| goto __error; | |
| } | |
| err = snd_config_load(top, input); | |
| snd_input_close(input); | |
| if (err < 0) { | |
| SNDERR("%s may be old or corrupted: consider to remove or fix it", file); | |
| goto __error; | |
| } | |
| err = find_full(class, mixer, top, priv->device); | |
| if (err >= 0) | |
| goto __full; | |
| } | |
| if (err >= 0) { | |
| err = snd_ctl_open(&priv->ctl, priv->device, 0); | |
| if (err < 0) { | |
| SNDERR("unable to open control device '%s': %s", priv->device, snd_strerror(err)); | |
| goto __error; | |
| } | |
| err = snd_hctl_open_ctl(&priv->hctl, priv->ctl); | |
| if (err < 0) | |
| goto __error; | |
| err = snd_ctl_card_info_malloc(&priv->info); | |
| if (err < 0) | |
| goto __error; | |
| err = snd_ctl_card_info(priv->ctl, priv->info); | |
| if (err < 0) | |
| goto __error; | |
| } | |
| if (err >= 0) | |
| err = find_module(class, top); | |
| if (err >= 0) | |
| err = snd_mixer_attach_hctl(mixer, priv->hctl); | |
| if (err >= 0) { | |
| priv->attach_flag = 1; | |
| err = snd_mixer_class_register(class, mixer); | |
| } | |
| __full: | |
| if (err < 0) { | |
| __error: | |
| if (top) | |
| snd_config_delete(top); | |
| if (class) | |
| snd_mixer_class_free(class); | |
| return err; | |
| } | |
| if (top) | |
| snd_config_delete(top); | |
| if (classp) | |
| *classp = class; | |
| return 0; | |
| } | |
| /** | |
| * \brief Basic Mixer Abstraction - Get information about device | |
| * \param class Mixer class | |
| * \param info Info structure | |
| * \return 0 on success otherwise a negative error code | |
| */ | |
| int snd_mixer_sbasic_info(const snd_mixer_class_t *class, sm_class_basic_t *info) | |
| { | |
| class_priv_t *priv = snd_mixer_class_get_private(class); | |
| if (class == NULL || info == NULL) | |
| return -EINVAL; | |
| info->device = priv->device; | |
| info->ctl = priv->ctl; | |
| info->hctl = priv->hctl; | |
| info->info = priv->info; | |
| return 0; | |
| } | |
| /** | |
| * \brief Get private data for basic abstraction | |
| * \param class Mixer class | |
| * \return private data | |
| */ | |
| void *snd_mixer_sbasic_get_private(const snd_mixer_class_t *class) | |
| { | |
| class_priv_t *priv = snd_mixer_class_get_private(class); | |
| if (class == NULL) | |
| return NULL; | |
| return priv->private_data; | |
| } | |
| /** | |
| * \brief Set private data for basic abstraction | |
| * \param class Mixer class | |
| * \param private_data Private data | |
| */ | |
| void snd_mixer_sbasic_set_private(const snd_mixer_class_t *class, void *private_data) | |
| { | |
| class_priv_t *priv; | |
| if (class == NULL) | |
| return; | |
| priv = snd_mixer_class_get_private(class); | |
| priv->private_data = private_data; | |
| } | |
| /** | |
| * \brief Set private data free callback for basic abstraction | |
| * \param class Mixer class | |
| * \param private_free free callback for private data | |
| */ | |
| void snd_mixer_sbasic_set_private_free(const snd_mixer_class_t *class, void (*private_free)(snd_mixer_class_t *class)) | |
| { | |
| class_priv_t *priv; | |
| if (class == NULL) | |
| return; | |
| priv = snd_mixer_class_get_private(class); | |
| priv->private_free = private_free; | |
| } |