]> mj.ucw.cz Git - osdd.git/commitdiff
osd-alsa: A new client for adjusting ALSA mixer controls
authorMartin Mares <mj@ucw.cz>
Sun, 15 Apr 2012 14:34:03 +0000 (16:34 +0200)
committerMartin Mares <mj@ucw.cz>
Sun, 15 Apr 2012 14:34:03 +0000 (16:34 +0200)
Makefile
README
osd-alsa.c [new file with mode: 0644]

index 40b135a094c2ba50ae6ad1f4f92a1dbae738a15f..eeee4394909d94a1eabb964f6e31d19f2352b2be 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -3,20 +3,24 @@ ARCHIVE=osdd-$(VERSION).tar.gz
 
 CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99
 
-all: osdd osdc osd-batt
+all: osdd osdc osd-batt osd-alsa
 
 osdd: osdd.o util.o
 osdc: osdc.o util.o client.o
 osd-batt: osd-batt.o util.o client.o
+osd-alsa: osd-alsa.o util.o client.o
 
 osdd.o: CFLAGS+=$(shell xosd-config --cflags)
-osdd: LDFLAGS+=$(shell xosd-config --libs)
+osdd: LDLIBS+=$(shell xosd-config --libs)
 
-osdc: LDFLAGS+=-lX11
-osd-batt: LDFLAGS+=-lX11
+osdc: LDLIBS+=-lX11
+osd-batt: LDLIBS+=-lX11
+
+osd-alsa.o: CFLAGS+=$(shell pkg-config --cflags alsa)
+osd-alsa: LDLIBS+=$(shell pkg-config --libs alsa) -lX11
 
 clean:
-       rm -f *~ *.o TAGS core osdd osdc osd-batt
+       rm -f *~ *.o TAGS core osdd osdc osd-batt osd-alsa
 
 release:
        git tag v$(VERSION)
diff --git a/README b/README
index c9b974da0bfe483018c2f4d074e9e4b50fa0a13a..4e1931609b76d2e6e319b411908e819523e58eb2 100644 (file)
--- a/README
+++ b/README
@@ -69,3 +69,5 @@ osd-batt              A slightly more complex client written in C, showing
                        the current status of laptop batteries. It can be asked
                        to show status immediately or to run in background and
                        croak whenever the battery is low.
+
+osd-alsa               Adjust volume of an ALSA mixer channel. Also written in 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;
+}