--- /dev/null
+/*
+ * 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;
+}