]> mj.ucw.cz Git - osdd.git/blobdiff - osd-alsa.c
osd-alsa: A new client for adjusting ALSA mixer controls
[osdd.git] / osd-alsa.c
diff --git a/osd-alsa.c b/osd-alsa.c
new file mode 100644 (file)
index 0000000..3d7bc27
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ *     A Simple ALSA Volume Control via OSD
+ *
+ *     (c) 2012 Martin Mares <mj@ucw.cz>
+ */
+
+#undef DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <alsa/asoundlib.h>
+
+#include "osd.h"
+
+static char *alsa_device = "default";
+static char *mixer_control = "Master";
+static int adjust_by;
+static int want_mute = -1;
+
+static snd_mixer_t *mixer;
+static snd_mixer_elem_t *elem;
+
+static void init_mixer(void)
+{
+  int err;
+
+  if (err = snd_mixer_open(&mixer, 0))
+    die("snd_mixer_open failed: error %d", err);
+
+  if (err = snd_mixer_attach(mixer, alsa_device))
+    die("snd_mixer_attach failed: error %d", err);
+
+  if (err = snd_mixer_selem_register(mixer, NULL, NULL))
+    die("snd_mixer_selem_register failed: error %d", err);
+
+  if (err = snd_mixer_load(mixer))
+    die("snd_mixer_load: error %d", err);
+
+  for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem))
+    {
+      const char *name = snd_mixer_selem_get_name(elem);
+      int index = snd_mixer_selem_get_index(elem);
+      if (!strcmp(name, mixer_control) || index)
+       {
+         if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
+           die("Unable to handle non-simple mixer controls");
+         if (!snd_mixer_selem_is_active(elem))
+           die("Selected mixer control is not active");
+         DBG("Found mixer control %s[%d]\n", name, index);
+         return;
+       }
+    }
+
+  die("Unable to find mixer control %s", mixer_control);
+}
+
+static int get_mute(void)
+{
+  int mute_on = 0, mute_off = 0;
+
+  if (snd_mixer_selem_has_playback_switch(elem))
+    {
+      for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++)
+       {
+         int val;
+         if (snd_mixer_selem_get_playback_switch(elem, ch, &val))
+           {
+             if (val)
+               mute_off++;
+             else
+               mute_on++;
+           }
+       }
+    }
+  DBG("Mute: on=%d off=%d\n", mute_on, mute_off);
+  return !!mute_on;
+}
+
+static long get_volume(long *pmin, long *pmax)
+{
+  long min, max, curr=0;
+  if (!snd_mixer_selem_has_playback_volume(elem) ||
+      snd_mixer_selem_get_playback_volume_range(elem, &min, &max))
+    {
+      *pmin = *pmax = 0;
+      return 0;
+    }
+
+  for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++)
+    {
+      long val;
+      if (snd_mixer_selem_get_playback_volume(elem, ch, &val))
+       {
+         if (val > curr)
+           curr = val;
+       }
+    }
+
+  DBG("Volume: min=%ld max=%ld curr=%ld\n", min, max, curr);
+  *pmin = min;
+  *pmax = max;
+  return curr;
+}
+
+static int vol_to_perc(long curr, long min, long max)
+{
+  return (100LL * (curr-min) + (max-min)/2) / (max-min);
+}
+
+static long perc_to_vol(int perc, long min, long max)
+{
+  return ((long long) perc * (max-min) + 50) / 100;
+}
+
+static void show_mixer(void)
+{
+  long min, max;
+  long curr = get_volume(&min, &max);
+  int muted = get_mute();
+
+  struct osd_msg *msg = osd_new_msg();
+  osd_add_line(msg, "min-duration", "0");
+  char buf[256];
+  snprintf(buf, sizeof(buf), "%s volume", mixer_control);
+  osd_add_line(msg, NULL, buf);
+  osd_add_line(msg, NULL, "");
+  if (muted)
+    osd_add_line(msg, NULL, "[mute]");
+  else if (min < max)
+    {
+      snprintf(buf, sizeof(buf), "%d", vol_to_perc(curr, min, max));
+      osd_add_line(msg, "slider", buf);
+    }
+  osd_send(msg);
+}
+
+
+static void set_mute(void)
+{
+  if (want_mute < 0)
+    return;
+
+  if (want_mute == 2)
+    want_mute = !get_mute();
+
+  if (snd_mixer_selem_has_playback_switch(elem))
+    {
+      for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++)
+       snd_mixer_selem_set_playback_switch(elem, ch, !want_mute);
+    }
+}
+
+static void set_volume(void)
+{
+  if (!adjust_by)
+    return;
+
+  long min, max;
+  long curr = get_volume(&min, &max);
+  int perc = vol_to_perc(curr, min, max);
+
+  DBG("Volume: have %d %ld\n", perc, curr);
+  perc += adjust_by;
+  if (perc < 0)
+    perc = 0;
+  if (perc > 100)
+    perc = 100;
+  curr = perc_to_vol(perc, min, max);
+  curr = min + (((long long) perc * (max-min) + 50) / 100);
+  DBG("Volume: want %d %ld\n", perc, curr);
+
+  for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++)
+    snd_mixer_selem_set_playback_volume(elem, ch, curr);
+}
+
+static void NONRET
+usage(void)
+{
+  fprintf(stderr, "\
+Usage: osd-alsa <options>\n\
+\n\
+Options:\n\
+-a, --adjust=<percent> Adjust the control by a given amount\n\
+-D, --device=<name>    ALSA device (default: `default')\n\
+-m, --mixer=<name>     Name of mixer control (default: `Master')\n\
+-0, --mute             Mute the control\n\
+-t, --toggle           Mute/unmute the control\n\
+-1, --unmute           Unmute the control\n\
+");
+  exit(1);
+}
+
+static const char short_opts[] = "01a:c:D:t";
+
+static const struct option long_opts[] = {
+  { "adjust",          required_argument,      NULL,   'a' },
+  { "device",          required_argument,      NULL,   'D' },
+  { "mixer",           required_argument,      NULL,   'm' },
+  { "mute",            no_argument,            NULL,   '0' },
+  { "toggle",          no_argument,            NULL,   't' },
+  { "unmute",          no_argument,            NULL,   '1' },
+  { NULL,              0,                      NULL,   0   },
+};
+
+int main(int argc, char **argv)
+{
+  int opt;
+  while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
+    switch (opt)
+      {
+      case '0':
+       want_mute = 1;
+       break;
+      case '1':
+       want_mute = 0;
+       break;
+      case 'a':
+       adjust_by = atoi(optarg);
+       break;
+      case 'D':
+       alsa_device = optarg;
+       break;
+      case 'm':
+       mixer_control = optarg;
+       break;
+      case 't':
+       want_mute = 2;
+       break;
+      default:
+       usage();
+      }
+  if (optind < argc)
+    usage();
+
+  init_mixer();
+  osd_init();
+  set_mute();
+  set_volume();
+  show_mixer();
+  return 0;
+}