/* * 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 "util.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); 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; }