]> git.sur5r.net Git - i3/i3status/blob - src/print_volume.c
b1a7e7401e49fcf6e94a611a7851698919c7339a
[i3/i3status] / src / print_volume.c
1 // vim:ts=4:sw=4:expandtab
2 #include <time.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <err.h>
7 #include <yajl/yajl_gen.h>
8 #include <yajl/yajl_version.h>
9
10 #ifdef LINUX
11 #include <alsa/asoundlib.h>
12 #include <alloca.h>
13 #endif
14
15 #if defined(__FreeBSD__) || defined(__DragonFly__)
16 #include <fcntl.h>
17 #include <unistd.h>
18 #include <sys/soundcard.h>
19 #endif
20
21 #ifdef __OpenBSD__
22 #include <fcntl.h>
23 #include <unistd.h>
24 #include <soundcard.h>
25 #endif
26
27 #include "i3status.h"
28 #include "queue.h"
29
30 static char *apply_volume_format(const char *fmt, char *outwalk, int ivolume) {
31     const char *walk = fmt;
32
33     for (; *walk != '\0'; walk++) {
34         if (*walk != '%') {
35             *(outwalk++) = *walk;
36             continue;
37         }
38         if (BEGINS_WITH(walk + 1, "%")) {
39             outwalk += sprintf(outwalk, "%%");
40             walk += strlen("%");
41         }
42         if (BEGINS_WITH(walk + 1, "volume")) {
43             outwalk += sprintf(outwalk, "%d%%", ivolume);
44             walk += strlen("volume");
45         }
46     }
47     return outwalk;
48 }
49
50 void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char *fmt_muted, const char *device, const char *mixer, int mixer_idx) {
51     char *outwalk = buffer;
52     int pbval = 1;
53
54     /* Printing volume works with ALSA and PulseAudio at the moment */
55     if (output_format == O_I3BAR) {
56         char *instance;
57         asprintf(&instance, "%s.%s.%d", device, mixer, mixer_idx);
58         INSTANCE(instance);
59         free(instance);
60     }
61
62     /* Try PulseAudio first */
63
64     /* If the device name has the format "pulse[:N]" where N is the
65      * index of the PulseAudio sink then force PulseAudio, optionally
66      * overriding the default sink */
67     if (!strncasecmp(device, "pulse", strlen("pulse"))) {
68         uint32_t sink_idx = device[5] == ':' ? (uint32_t)atoi(device + 6)
69                                              : DEFAULT_SINK_INDEX;
70         int cvolume = pulse_initialize() ? volume_pulseaudio(sink_idx) : 0;
71         int ivolume = DECOMPOSE_VOLUME(cvolume);
72         bool muted = DECOMPOSE_MUTED(cvolume);
73         if (muted) {
74             START_COLOR("color_degraded");
75             pbval = 0;
76         }
77         /* negative result means error, stick to 0 */
78         if (ivolume < 0)
79             ivolume = 0;
80         outwalk = apply_volume_format(muted ? fmt_muted : fmt,
81                                       outwalk,
82                                       ivolume);
83         goto out;
84     } else if (!strcasecmp(device, "default") && pulse_initialize()) {
85         /* no device specified or "default" set */
86         int cvolume = volume_pulseaudio(DEFAULT_SINK_INDEX);
87         int ivolume = DECOMPOSE_VOLUME(cvolume);
88         bool muted = DECOMPOSE_MUTED(cvolume);
89         if (ivolume >= 0) {
90             if (muted) {
91                 START_COLOR("color_degraded");
92                 pbval = 0;
93             }
94             outwalk = apply_volume_format(muted ? fmt_muted : fmt,
95                                           outwalk,
96                                           ivolume);
97             goto out;
98         }
99         /* negative result means error, fail PulseAudio attempt */
100     }
101 /* If some other device was specified or PulseAudio is not detected,
102  * proceed to ALSA / OSS */
103
104 #ifdef LINUX
105     int err;
106     snd_mixer_t *m;
107     snd_mixer_selem_id_t *sid;
108     snd_mixer_elem_t *elem;
109     long min, max, val;
110     int avg;
111
112     if ((err = snd_mixer_open(&m, 0)) < 0) {
113         fprintf(stderr, "i3status: ALSA: Cannot open mixer: %s\n", snd_strerror(err));
114         goto out;
115     }
116
117     /* Attach this mixer handle to the given device */
118     if ((err = snd_mixer_attach(m, device)) < 0) {
119         fprintf(stderr, "i3status: ALSA: Cannot attach mixer to device: %s\n", snd_strerror(err));
120         snd_mixer_close(m);
121         goto out;
122     }
123
124     /* Register this mixer */
125     if ((err = snd_mixer_selem_register(m, NULL, NULL)) < 0) {
126         fprintf(stderr, "i3status: ALSA: snd_mixer_selem_register: %s\n", snd_strerror(err));
127         snd_mixer_close(m);
128         goto out;
129     }
130
131     if ((err = snd_mixer_load(m)) < 0) {
132         fprintf(stderr, "i3status: ALSA: snd_mixer_load: %s\n", snd_strerror(err));
133         snd_mixer_close(m);
134         goto out;
135     }
136
137     snd_mixer_selem_id_malloc(&sid);
138     if (sid == NULL) {
139         snd_mixer_close(m);
140         goto out;
141     }
142
143     /* Find the given mixer */
144     snd_mixer_selem_id_set_index(sid, mixer_idx);
145     snd_mixer_selem_id_set_name(sid, mixer);
146     if (!(elem = snd_mixer_find_selem(m, sid))) {
147         fprintf(stderr, "i3status: ALSA: Cannot find mixer %s (index %i)\n",
148                 snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
149         snd_mixer_close(m);
150         snd_mixer_selem_id_free(sid);
151         goto out;
152     }
153
154     /* Get the volume range to convert the volume later */
155     snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
156
157     snd_mixer_handle_events(m);
158     snd_mixer_selem_get_playback_volume(elem, 0, &val);
159     if (max != 100) {
160         float avgf = ((float)val / max) * 100;
161         avg = (int)avgf;
162         avg = (avgf - avg < 0.5 ? avg : (avg + 1));
163     } else
164         avg = (int)val;
165
166     /* Check for mute */
167     if (snd_mixer_selem_has_playback_switch(elem)) {
168         if ((err = snd_mixer_selem_get_playback_switch(elem, 0, &pbval)) < 0)
169             fprintf(stderr, "i3status: ALSA: playback_switch: %s\n", snd_strerror(err));
170         if (!pbval) {
171             START_COLOR("color_degraded");
172             fmt = fmt_muted;
173         }
174     }
175
176     snd_mixer_close(m);
177     snd_mixer_selem_id_free(sid);
178
179     outwalk = apply_volume_format(fmt, outwalk, avg);
180
181 #endif
182 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
183     char *mixerpath;
184     char defaultmixer[] = "/dev/mixer";
185     int mixfd, vol, devmask = 0;
186     pbval = 1;
187
188     if (mixer_idx > 0)
189         asprintf(&mixerpath, "/dev/mixer%d", mixer_idx);
190     else
191         mixerpath = defaultmixer;
192
193     if ((mixfd = open(mixerpath, O_RDWR)) < 0) {
194         warn("OSS: Cannot open mixer");
195         goto out;
196     }
197
198     if (mixer_idx > 0)
199         free(mixerpath);
200
201     if (ioctl(mixfd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) {
202         warn("OSS: Cannot read mixer information");
203         goto out;
204     }
205     if (ioctl(mixfd, MIXER_READ(0), &vol) == -1) {
206         warn("OSS: Cannot read mixer information");
207         goto out;
208     }
209
210     if (((vol & 0x7f) == 0) && (((vol >> 8) & 0x7f) == 0)) {
211         START_COLOR("color_degraded");
212         pbval = 0;
213     }
214
215     outwalk = apply_volume_format(fmt, outwalk, vol & 0x7f);
216     close(mixfd);
217 #endif
218
219 out:
220     *outwalk = '\0';
221     if (!pbval)
222         END_COLOR;
223     OUTPUT_FULL_TEXT(buffer);
224 }