Logo Search packages:      
Sourcecode: pbbuttonsd version File versions  Download package

module_alsamixer.c

/* --------------------------------------------------------------------------
 * module_alsamixer.c
 * code for the ALSA mixer module, controling volume levels
 *
 * Copyright 2004 John Steele Scott <toojays@toojays.net>
 * Copyright 2002 Matthias Grimm
 * Copyright 1999-2000 by Jaroslav Kysela <perex@suse.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.
 * -------------------------------------------------------------------------*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#ifdef WITH_ALSA

#include <alsa/asoundlib.h>
#include "pbbinput.h"

#include <math.h>
#include <pbb.h>

#include "gettext_macros.h"
#include "input_manager.h"
#include "config_manager.h"
#include "module_mixer.h"
#include "module_alsamixer.h"
#include "support.h"

struct moddata_alsamixer {
      char *card;  /* name of the soundcard (usually "default" is fine) */
      char *channels;         /* the "MixerChannels" config string */
      unsigned short keyvolup;
      unsigned short modvolup;
      unsigned short keyvoldn;
      unsigned short modvoldn;
      unsigned short keymute;
      unsigned short modmute;
      int init_complete;
      int mixer_delayed;
      int mute;
      int open;
      snd_mixer_t *mixer;
      snd_mixer_elem_t **elements; /* points to a NULL terminated list, first is master */
} modbase_alsamixer;

int
alsamixer_init ()
{
      struct moddata_alsamixer *base = &modbase_alsamixer;
      static char devbuffer_mixer[STDBUFFERLEN];
      static char mixer_channels[STDBUFFERLEN];
      int sid;

      base->card            = devbuffer_mixer;
      base->channels          = mixer_channels;
      base->elements      = NULL;
      base->mixer             = NULL;
      base->keyvolup          = KEY_VOLUMEUP;
      base->modvolup          = MOD_NONE;
      base->keyvoldn          = KEY_VOLUMEDOWN;
      base->modvoldn          = MOD_NONE;
      base->keymute           = KEY_MUTE;
      base->modmute           = MOD_NONE;
      base->mixer_delayed = 0;
      base->init_complete = 0;
      base->open          = 0;

      if ((sid = registerCfgSection ("MODULE MIXER")) == 0) {
            print_msg (PBB_ERR, _("Can't register configuration section %s, out of memory."), "MODULE MIXER");
            return E_NOMEM;
      } else {
            /* options always to process */
            registerCfgOptionString (sid, "ALSA_Card", TAG_MIXERCARD, 0,
                        NULL);
            registerCfgOptionString (sid, "ALSA_Elements", TAG_MIXERELEMENTS, 0,
                        NULL);
            registerCfgOptionString (sid, "MixerCard", TAG_MIXERCARD, FLG_RONLY,
                        NULL);
            
            /* options only to process if this module is active (open) */
            registerCfgOptionInt (sid, "Volume", TAG_VOLUME, 0,
                        N_("initial volume level"));
            registerCfgOptionBool (sid, "Speakers_muted", TAG_MUTE, 0,
                        N_("mute after startup?"));
            registerCfgOptionKey (sid, "VolumeUpKey", TAG_VOLUMEUPKEY, TAG_VOLUMEUPMOD, 0,
                        NULL);
            registerCfgOptionKey (sid, "VolumeDownKey", TAG_VOLUMEDOWNKEY, TAG_VOLUMEDOWNMOD, 0,
                        NULL);
            registerCfgOptionKey (sid, "MuteKey", TAG_MUTEKEY, TAG_MUTEMOD, 0,
                        NULL);
            registerCfgOptionBool (sid, "MixerInitDelay", TAG_MIXERINITDELAY, 0,
                        NULL);
      }
      register_function (QUERYQUEUE, alsamixer_query);
      register_function (CONFIGQUEUE, alsamixer_configure);
      return 0;
}

int
alsamixer_exit ()
{
      return 0;
}

/* initialise the list of elements, based on the "channels" string */
static int
alsamixer_setup_elements_internal (char * channels, snd_mixer_selem_id_t *sid)
{
      struct moddata_alsamixer *base = &modbase_alsamixer;
    char *token;
      char buffer[STDBUFFERLEN];
      int n;

      /* get rid of old list */
      if (base->elements) {
            free (base->elements);
            base->elements = NULL;
      }
      base->init_complete = 0;
      n = 0;

      /* this copy we save for returning in alsamixer_handle_tags */
      if (base->channels != channels) /* don't copy base->channels on top of itself */
            strncpy (base->channels, channels, STDBUFFERLEN);

      /* this copy we use for strtok */
      strncpy (buffer, channels, STDBUFFERLEN);
      cleanup_buffer (buffer);
      if ((token = strtok(buffer, ",\n"))) {
            do {
                  /* ensure we have enough memory to store pointer + terminator */
                  base->elements = realloc (base->elements, (n+2)*sizeof (snd_mixer_elem_t *));
                  if (base->elements == NULL) {
                        print_msg (PBB_ERR, _("Memory allocation failed.\n"));
                        return -1;
                  }

                  /* find element */
                  for (base->elements[n] = snd_mixer_first_elem (base->mixer);
                       base->elements[n] != NULL;
                       base->elements[n] = snd_mixer_elem_next (base->elements[n])) {
                        snd_mixer_selem_get_id (base->elements[n], sid);
                        if (strcasecmp (snd_mixer_selem_id_get_name (sid), token) == 0)
                              break;
                  }
                  
                  if (base->elements[n] == NULL) {
                        /* report any element we can't find */
                        print_msg (PBB_WARN, _("Card '%s' has no '%s' element.\n"), base->card, token);
                  } else {
                        n++;
                  }
                  
            } while ((token = strtok(0,",\n")) != NULL);

            /* terminate list */
            base->elements[n] = NULL;

            /* check that we have a master element */
            if (!base->elements[0]) {
                  print_msg (PBB_WARN, _("Option 'ALSA_Elements' contains no valid elements\n"));
                  return -1;
            }

            /* check that master element has a playback volume */
            if (!snd_mixer_selem_has_playback_volume (base->elements[0])) {
                  snd_mixer_selem_get_id (base->elements[n], sid);
                  print_msg (PBB_WARN, _("Mixer element '%s' has no playback volume control.\n"), snd_mixer_selem_id_get_name (sid));
                  return -1;
            }

            base->init_complete = 1;
            return 0;
      } else {
            /* no tokens? */
            return -1;
      }
}

/* wrapper arround alsamixer_setup_elements_internal so we can be sure to free the sid. */
static int
alsamixer_setup_elements (char * channels)
{
      snd_mixer_selem_id_t *sid;
      int err, result;

      /* create a selem_id to extract names from */
      if ((err = snd_mixer_selem_id_malloc (&sid))){
            print_msg (PBB_ERR, _("Memory allocation failed: %s\n"), snd_strerror (err));
            return -1;
      }

      result = alsamixer_setup_elements_internal (channels, sid);
      snd_mixer_selem_id_free (sid);
      return result;
}

static int
alsamixer_finish_init ()
{
      struct moddata_alsamixer *base = &modbase_alsamixer;
      snd_mixer_t *mixer;
      int err;

      /* setup mixer */
      if ((err = snd_mixer_open (&mixer, 0)) < 0) {
            print_msg (PBB_ERR, _("Can't open card '%s': %s\n"), base->card, snd_strerror (err));
            return -1;
      }
      if ((err = snd_mixer_attach (mixer, base->card)) < 0) {
            print_msg (PBB_ERR, _("Can't attach card '%s': %s\n"), base->card, snd_strerror (err));
            snd_mixer_close (mixer);
            return -1;
      }
      if ((err = snd_mixer_selem_register (mixer, NULL, NULL)) < 0) {
            print_msg (PBB_ERR, _("Can't register mixer: %s\n"), snd_strerror (err));
            snd_mixer_detach (mixer, base->card);
            snd_mixer_close (mixer);
            return -1;
      }
      
      if ((err = snd_mixer_load (mixer)) < 0) {
            print_msg (PBB_ERR, _("Can't load card '%s': %s\n"), base->card, snd_strerror (err));
            snd_mixer_detach (mixer, base->card);
            snd_mixer_close (mixer);
            return -1;
      }

      base->mixer = mixer;
      return alsamixer_setup_elements (base->channels);
}

/* Converts an ALSA volume to a number in the range [0, +/-100]. This function
 * assumes that the mixer has already been setup successfully.
 */
static int
alsamixer_vol_to_percentage (snd_mixer_elem_t * elem, long volume)
{
      long volmin, volmax;
      int pct;

      snd_mixer_selem_get_playback_volume_range (elem, &volmin, &volmax);
      pct = volmax == volmin ? 0 : rint(volume*100.0/(volmax-volmin));
      if (pct > 100) pct = 100;
      if (pct < 0)   pct = 0;
      return pct;
}

/* Converts a number in the range [0, +/-100] to an ALSA volume. This function
 * assumes that the mixer has already been setup successfully.
 */
static long
alsamixer_percentage_to_vol (snd_mixer_elem_t * elem, int pct)
{
      long volmin, volmax;
      
      snd_mixer_selem_get_playback_volume_range (elem, &volmin, &volmax);
      return rint((volmax-volmin)*pct/100.0);
}

/* Saves the current mute status in base->mute, and returns the volume of the first playback channel of the master element */
static int
alsamixer_get_volume ()
{
      struct moddata_alsamixer *base = &modbase_alsamixer;
      long volume;
      int mute;

      if (base->init_complete == 0) /* is mixer setup already completed? */
            if ((alsamixer_finish_init ()) != 0) /* no, then do it now */
                  return -1; /* Oops, try it again later */

      /* force simple mixer to update its values from audio driver */
      snd_mixer_handle_events (base->mixer);

      /* read values */
      snd_mixer_selem_get_playback_switch (base->elements[0], 0, &mute);
      snd_mixer_selem_get_playback_volume (base->elements[0], 0, &volume);
      base->mute = !mute;
      return volume;
}

/* same as alsamixer_get_volume, but returns the volume as number between 0 and 100 */
static int 
alsamixer_get_volume_as_percentage ()
{
      struct moddata_alsamixer *base = &modbase_alsamixer;
      int volume;

      volume = alsamixer_get_volume ();
      return volume < 0 ? volume : alsamixer_vol_to_percentage (base->elements[0], volume);
}

static void
alsamixer_set_and_send (enum alsainc type, int value)
{
      struct moddata_alsamixer *base = &modbase_alsamixer;
      long master = 0, vol, min, max;
      int n;

      if (base->init_complete == 0) /* is mixer setup already completed? */
            if ((alsamixer_finish_init ()) != 0) /* no, then do it now */
                  return;     /* Oops, try it again later */

      base->mute = value ? 0 : !base->mute;
      
      /* cycle through elements */
      for (n=0; base->elements[n] != NULL; n++) {
            
            /* sync playback switch */
            snd_mixer_selem_set_playback_switch_all (base->elements[n], !base->mute);
            
            if (n == 0) {
                  switch (type) {
                  case ALSAMIXER_REL:
                        snd_mixer_selem_get_playback_volume_range (base->elements[0], &min, &max);
                        if (max > 100) 
                              value *= max/100 + 1;
                        value += alsamixer_get_volume ();
                  case ALSAMIXER_ABS:
                        master = alsamixer_vol_to_percentage (base->elements[0], value);
                        break;
                  case ALSAMIXER_REL_PERCENT:
                        value += alsamixer_get_volume_as_percentage ();
                  case ALSAMIXER_ABS_PERCENT:
                        if (value > 100) value = 100;
                        if (value < 0)   value = 0;
                        master = value;
                        break;
                  }
            }
            
            /* sync volume with master element, if not muted */
            if (!base->mute && snd_mixer_selem_has_playback_volume (base->elements[n])) {
                  vol = alsamixer_percentage_to_vol (base->elements[n], master);
                  snd_mixer_selem_set_playback_volume_all (base->elements[n], vol);
            }
      }
                
      if (base->mute)
            singletag_to_clients (CHANGEVALUE, TAG_MUTE, 0);
      else
            singletag_to_clients (CHANGEVALUE, TAG_VOLUME, master);
}

void
alsamixer_handle_tags (int cfgure, struct tagitem *taglist)
{
      struct moddata_alsamixer *base = &modbase_alsamixer;
      char *cardname, *elements;

      while (taglist->tag != TAG_END) {
            /* tags that would always be processed even if this module is not open
             */
            switch (taglist->tag) {
            case TAG_MIXERCARD:
                  if (cfgure) {
                        cardname = (char *) taglist->data;
                        if (strlen (cardname) < STDBUFFERLEN) {
                              strcpy (base->card, cardname);
                              alsamixer_close (); /* reinit mixer with next use */
                        } else {
                              print_msg (PBB_ERR, _("ALSA Card name too long. Buffer overflow\n"));         
                              tagerror (taglist, E_BUFOVL);
                        }
                  } else
                        taglist->data = (long) base->card;
                  break;
            case TAG_MIXERELEMENTS:
                  if (cfgure) {
                        elements = (char *) taglist->data;
                        if (strlen (elements) < STDBUFFERLEN) {
                              strcpy (base->channels, elements);
                              alsamixer_close (); /* reinit mixer with next use */
                        } else {
                              print_msg (PBB_ERR, _("Too much ALSA elements given. Buffer overflow.\n"));
                              tagerror (taglist, E_BUFOVL);
                        }
                  } else
                        taglist->data = (long) base->channels;
                  break;
            }     

            /* tags that will be processed only if the module is open.
             */
            if (base->open) {
                  switch (taglist->tag) {
                  case TAG_VOLUME:
                        if (cfgure) alsamixer_set_and_send (ALSAMIXER_ABS_PERCENT, taglist->data);
                        else        taglist->data = alsamixer_get_volume_as_percentage ();
                        break;
                  case TAG_MUTE:
                        if (cfgure) {
                              if (base->mute != taglist->data)
                                    alsamixer_set_and_send (ALSAMIXER_REL, 0);
                        } else {
                              /* update mute status */
                              alsamixer_get_volume ();
                              taglist->data = base->mute;
                        }
                        break;
                  }
            }
            
            /* tags that could be configured even the module is closed but only be read
             * if the module is open.
             */
            if (cfgure || base->open) {
                  switch (taglist->tag) {
                  case TAG_VOLUMEUPKEY:
                        if (cfgure) base->keyvolup = taglist->data;
                        else        taglist->data = base->keyvolup;
                        break;
                  case TAG_VOLUMEUPMOD:
                        if (cfgure) base->modvolup = taglist->data;
                        else        taglist->data = base->modvolup;
                        break;
                  case TAG_VOLUMEDOWNKEY:
                        if (cfgure) base->keyvoldn = taglist->data;
                        else        taglist->data = base->keyvoldn;
                        break;
                  case TAG_VOLUMEDOWNMOD:
                        if (cfgure) base->modvoldn = taglist->data;
                        else        taglist->data = base->modvoldn;
                        break;
                  case TAG_MUTEKEY:
                        if (cfgure) base->keymute = taglist->data;
                        else        taglist->data = base->keymute;
                        break;
                  case TAG_MUTEMOD:
                        if (cfgure) base->modmute = taglist->data;
                        else        taglist->data = base->modmute;
                        break;
                  case TAG_MIXERINITDELAY:
                        if (cfgure) base->mixer_delayed  = taglist->data;
                        else        taglist->data = base->mixer_delayed;
                        break;
                  }
            }
            taglist++;
      }
}

int
alsamixer_open (struct tagitem *taglist)
{
      struct moddata_alsamixer *base = &modbase_alsamixer;
      char *cardname, *channels;

      cardname = (char *) tagfind (taglist, TAG_MIXERCARD, (long) "default");
      if (strlen (cardname) < STDBUFFERLEN)
            strncpy (base->card, cardname, STDBUFFERLEN);
      else
            return E_BUFOVL;

      channels = (char *) tagfind (taglist, TAG_MIXERELEMENTS, (long) "Master");
      if (strlen (channels) < STDBUFFERLEN)
            strncpy (base->channels, channels, STDBUFFERLEN);
      else
            return E_BUFOVL;

      if ((base->mixer_delayed = tagfind (taglist, TAG_MIXERINITDELAY, 0)) == 0)
            if (alsamixer_finish_init () == -1)
                  return E_NOSUPPORT;
            
      if (!base->init_complete) {
            /* can't set volume with only partly initialized mixer */
            tagskip (taglist, TAG_VOLUME);
            tagskip (taglist, TAG_MUTE);
      }

      base->open = 1;
      register_function (KBDQUEUE, alsamixer_keyboard);
      return 0;
}

int
alsamixer_close ()
{
      struct moddata_alsamixer *base = &modbase_alsamixer;

      if (base->elements) {
            free (base->elements);
            base->elements = NULL;
      }

      if (base->mixer) {
            snd_mixer_detach (base->mixer, base->card);
            snd_mixer_close (base->mixer);
            base->mixer = NULL;
      }
      
      base->init_complete = 0;
/*    base->open          = 0; */
      return 0;
}

void
alsamixer_keyboard (struct tagitem *taglist)
{
      struct moddata_alsamixer *base = &modbase_alsamixer;
      int code, value, mod, step;

      code = (int) tagfind (taglist, TAG_KEYCODE, 0);
      value = (int) tagfind (taglist, TAG_KEYREPEAT, 0);
      mod = (int) tagfind (taglist, TAG_MODIFIER, 0);

      if (value) {
            if ((code == base->keyvolup) && ((mod & ~MOD_SHIFT) == base->modvolup)) {
                  step = (mod & MOD_SHIFT) ? +1 : +10;
            } else if ((code == base->keyvoldn) && ((mod & ~MOD_SHIFT) == base->modvoldn)) {
                  step = (mod & MOD_SHIFT) ? -1 : -10;
            } else if ((code == base->keymute) && (mod == base->modmute) && (value == 1)) {
                  step = 0;
            } else return;

            if ((step == 0) || (mod & MOD_SHIFT))
                  alsamixer_set_and_send(ALSAMIXER_REL, step); /* mute and fine tuning */
            else
                  alsamixer_set_and_send(ALSAMIXER_REL_PERCENT, step); /* normal control */
      }
}

void
alsamixer_query (struct tagitem *taglist)
{
      alsamixer_handle_tags (MODE_QUERY, taglist);
}

void
alsamixer_configure (struct tagitem *taglist)
{
      alsamixer_handle_tags (MODE_CONFIG, taglist);
}

#endif /* WITH_ALSA */


Generated by  Doxygen 1.6.0   Back to index