]> mj.ucw.cz Git - osdd.git/blob - osd-alsa.c
Merge branch 'master' of ssh://git.ucw.cz/home/mj/GIT/osdd
[osdd.git] / osd-alsa.c
1 /*
2  *      A Simple ALSA Volume Control via OSD
3  *
4  *      (c) 2012 Martin Mares <mj@ucw.cz>
5  */
6
7 #undef DEBUG
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <getopt.h>
13 #include <alsa/asoundlib.h>
14
15 #include "osd.h"
16
17 static char *alsa_device = "default";
18 static char *mixer_control = "Master";
19 static int adjust_by;
20 static int want_mute = -1;
21
22 static snd_mixer_t *mixer;
23 static snd_mixer_elem_t *elem;
24
25 static void init_mixer(void)
26 {
27   int err;
28
29   if (err = snd_mixer_open(&mixer, 0))
30     die("snd_mixer_open failed: error %d", err);
31
32   if (err = snd_mixer_attach(mixer, alsa_device))
33     die("snd_mixer_attach failed: error %d", err);
34
35   if (err = snd_mixer_selem_register(mixer, NULL, NULL))
36     die("snd_mixer_selem_register failed: error %d", err);
37
38   if (err = snd_mixer_load(mixer))
39     die("snd_mixer_load: error %d", err);
40
41   for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem))
42     {
43       const char *name = snd_mixer_selem_get_name(elem);
44       int index = snd_mixer_selem_get_index(elem);
45       if (!strcmp(name, mixer_control) || index)
46         {
47           if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
48             die("Unable to handle non-simple mixer controls");
49           if (!snd_mixer_selem_is_active(elem))
50             die("Selected mixer control is not active");
51           DBG("Found mixer control %s[%d]\n", name, index);
52           return;
53         }
54     }
55
56   die("Unable to find mixer control %s", mixer_control);
57 }
58
59 static int get_mute(void)
60 {
61   int mute_on = 0, mute_off = 0;
62
63   if (snd_mixer_selem_has_playback_switch(elem))
64     {
65       for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++)
66         {
67           int val;
68           if (snd_mixer_selem_get_playback_switch(elem, ch, &val))
69             {
70               if (val)
71                 mute_off++;
72               else
73                 mute_on++;
74             }
75         }
76     }
77   DBG("Mute: on=%d off=%d\n", mute_on, mute_off);
78   return !!mute_on;
79 }
80
81 static long get_volume(long *pmin, long *pmax)
82 {
83   long min, max, curr=0;
84   if (!snd_mixer_selem_has_playback_volume(elem) ||
85       snd_mixer_selem_get_playback_volume_range(elem, &min, &max))
86     {
87       *pmin = *pmax = 0;
88       return 0;
89     }
90
91   for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++)
92     {
93       long val;
94       if (snd_mixer_selem_get_playback_volume(elem, ch, &val))
95         {
96           if (val > curr)
97             curr = val;
98         }
99     }
100
101   DBG("Volume: min=%ld max=%ld curr=%ld\n", min, max, curr);
102   *pmin = min;
103   *pmax = max;
104   return curr;
105 }
106
107 static int vol_to_perc(long curr, long min, long max)
108 {
109   return (100LL * (curr-min) + (max-min)/2) / (max-min);
110 }
111
112 static long perc_to_vol(int perc, long min, long max)
113 {
114   return ((long long) perc * (max-min) + 50) / 100;
115 }
116
117 static void show_mixer(void)
118 {
119   long min, max;
120   long curr = get_volume(&min, &max);
121   int muted = get_mute();
122
123   struct osd_msg *msg = osd_new_msg();
124   osd_add_line(msg, "min-duration", "0");
125   char buf[256];
126   snprintf(buf, sizeof(buf), "%s volume", mixer_control);
127   osd_add_line(msg, NULL, buf);
128   osd_add_line(msg, NULL, "");
129   if (muted)
130     osd_add_line(msg, NULL, "[mute]");
131   else if (min < max)
132     {
133       snprintf(buf, sizeof(buf), "%d", vol_to_perc(curr, min, max));
134       osd_add_line(msg, "slider", buf);
135     }
136   osd_send(msg);
137 }
138
139
140 static void set_mute(void)
141 {
142   if (want_mute < 0)
143     return;
144
145   if (want_mute == 2)
146     want_mute = !get_mute();
147
148   if (snd_mixer_selem_has_playback_switch(elem))
149     {
150       for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++)
151         snd_mixer_selem_set_playback_switch(elem, ch, !want_mute);
152     }
153 }
154
155 static void set_volume(void)
156 {
157   if (!adjust_by)
158     return;
159
160   long min, max;
161   long curr = get_volume(&min, &max);
162   int perc = vol_to_perc(curr, min, max);
163
164   DBG("Volume: have %d %ld\n", perc, curr);
165   perc += adjust_by;
166   if (perc < 0)
167     perc = 0;
168   if (perc > 100)
169     perc = 100;
170   curr = perc_to_vol(perc, min, max);
171   curr = min + (((long long) perc * (max-min) + 50) / 100);
172   DBG("Volume: want %d %ld\n", perc, curr);
173
174   for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++)
175     snd_mixer_selem_set_playback_volume(elem, ch, curr);
176 }
177
178 static void NONRET
179 usage(void)
180 {
181   fprintf(stderr, "\
182 Usage: osd-alsa <options>\n\
183 \n\
184 Options:\n\
185 -a, --adjust=<percent>  Adjust the control by a given amount\n\
186 -D, --device=<name>     ALSA device (default: `default')\n\
187 -m, --mixer=<name>      Name of mixer control (default: `Master')\n\
188 -0, --mute              Mute the control\n\
189 -t, --toggle            Mute/unmute the control\n\
190 -1, --unmute            Unmute the control\n\
191 ");
192   exit(1);
193 }
194
195 static const char short_opts[] = "01a:c:D:t";
196
197 static const struct option long_opts[] = {
198   { "adjust",           required_argument,      NULL,   'a' },
199   { "device",           required_argument,      NULL,   'D' },
200   { "mixer",            required_argument,      NULL,   'm' },
201   { "mute",             no_argument,            NULL,   '0' },
202   { "toggle",           no_argument,            NULL,   't' },
203   { "unmute",           no_argument,            NULL,   '1' },
204   { NULL,               0,                      NULL,   0   },
205 };
206
207 int main(int argc, char **argv)
208 {
209   int opt;
210   while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
211     switch (opt)
212       {
213       case '0':
214         want_mute = 1;
215         break;
216       case '1':
217         want_mute = 0;
218         break;
219       case 'a':
220         adjust_by = atoi(optarg);
221         break;
222       case 'D':
223         alsa_device = optarg;
224         break;
225       case 'm':
226         mixer_control = optarg;
227         break;
228       case 't':
229         want_mute = 2;
230         break;
231       default:
232         usage();
233       }
234   if (optind < argc)
235     usage();
236
237   init_mixer();
238   osd_init();
239   set_mute();
240   set_volume();
241   show_mixer();
242   return 0;
243 }