]> git.sur5r.net Git - i3/i3status/blob - src/print_volume.c
Added support for lemonbar
[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 <sys/audioio.h>
25 #include <sys/ioctl.h>
26 #endif
27
28 #include "i3status.h"
29 #include "queue.h"
30
31 static char *apply_volume_format(const char *fmt, char *outwalk, int ivolume) {
32     const char *walk = fmt;
33
34     for (; *walk != '\0'; walk++) {
35         if (*walk != '%') {
36             *(outwalk++) = *walk;
37             continue;
38         }
39         if (BEGINS_WITH(walk + 1, "%")) {
40             outwalk += sprintf(outwalk, "%s", pct_mark);
41             walk += strlen("%");
42         }
43         if (BEGINS_WITH(walk + 1, "volume")) {
44             outwalk += sprintf(outwalk, "%d%s", ivolume, pct_mark);
45             walk += strlen("volume");
46         }
47     }
48     return outwalk;
49 }
50
51 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) {
52     char *outwalk = buffer;
53     int pbval = 1;
54
55     /* Printing volume works with ALSA and PulseAudio at the moment */
56     if (output_format == O_I3BAR) {
57         char *instance;
58         asprintf(&instance, "%s.%s.%d", device, mixer, mixer_idx);
59         INSTANCE(instance);
60         free(instance);
61     }
62
63 #ifndef __OpenBSD__
64     /* Try PulseAudio first */
65
66     /* If the device name has the format "pulse[:N]" where N is the
67      * index of the PulseAudio sink then force PulseAudio, optionally
68      * overriding the default sink */
69     if (!strncasecmp(device, "pulse", strlen("pulse"))) {
70         uint32_t sink_idx = device[5] == ':' ? (uint32_t)atoi(device + 6)
71                                              : DEFAULT_SINK_INDEX;
72         int cvolume = pulse_initialize() ? volume_pulseaudio(sink_idx) : 0;
73         int ivolume = DECOMPOSE_VOLUME(cvolume);
74         bool muted = DECOMPOSE_MUTED(cvolume);
75         if (muted) {
76             START_COLOR("color_degraded");
77             pbval = 0;
78         }
79         /* negative result means error, stick to 0 */
80         if (ivolume < 0)
81             ivolume = 0;
82         outwalk = apply_volume_format(muted ? fmt_muted : fmt,
83                                       outwalk,
84                                       ivolume);
85         goto out;
86     } else if (!strcasecmp(device, "default") && pulse_initialize()) {
87         /* no device specified or "default" set */
88         int cvolume = volume_pulseaudio(DEFAULT_SINK_INDEX);
89         int ivolume = DECOMPOSE_VOLUME(cvolume);
90         bool muted = DECOMPOSE_MUTED(cvolume);
91         if (ivolume >= 0) {
92             if (muted) {
93                 START_COLOR("color_degraded");
94                 pbval = 0;
95             }
96             outwalk = apply_volume_format(muted ? fmt_muted : fmt,
97                                           outwalk,
98                                           ivolume);
99             goto out;
100         }
101         /* negative result means error, fail PulseAudio attempt */
102     }
103 /* If some other device was specified or PulseAudio is not detected,
104  * proceed to ALSA / OSS */
105 #endif
106
107 #ifdef LINUX
108     int err;
109     snd_mixer_t *m;
110     snd_mixer_selem_id_t *sid;
111     snd_mixer_elem_t *elem;
112     long min, max, val;
113     int avg;
114
115     if ((err = snd_mixer_open(&m, 0)) < 0) {
116         fprintf(stderr, "i3status: ALSA: Cannot open mixer: %s\n", snd_strerror(err));
117         goto out;
118     }
119
120     /* Attach this mixer handle to the given device */
121     if ((err = snd_mixer_attach(m, device)) < 0) {
122         fprintf(stderr, "i3status: ALSA: Cannot attach mixer to device: %s\n", snd_strerror(err));
123         snd_mixer_close(m);
124         goto out;
125     }
126
127     /* Register this mixer */
128     if ((err = snd_mixer_selem_register(m, NULL, NULL)) < 0) {
129         fprintf(stderr, "i3status: ALSA: snd_mixer_selem_register: %s\n", snd_strerror(err));
130         snd_mixer_close(m);
131         goto out;
132     }
133
134     if ((err = snd_mixer_load(m)) < 0) {
135         fprintf(stderr, "i3status: ALSA: snd_mixer_load: %s\n", snd_strerror(err));
136         snd_mixer_close(m);
137         goto out;
138     }
139
140     snd_mixer_selem_id_malloc(&sid);
141     if (sid == NULL) {
142         snd_mixer_close(m);
143         goto out;
144     }
145
146     /* Find the given mixer */
147     snd_mixer_selem_id_set_index(sid, mixer_idx);
148     snd_mixer_selem_id_set_name(sid, mixer);
149     if (!(elem = snd_mixer_find_selem(m, sid))) {
150         fprintf(stderr, "i3status: ALSA: Cannot find mixer %s (index %i)\n",
151                 snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
152         snd_mixer_close(m);
153         snd_mixer_selem_id_free(sid);
154         goto out;
155     }
156
157     /* Get the volume range to convert the volume later */
158     snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
159
160     snd_mixer_handle_events(m);
161     snd_mixer_selem_get_playback_volume(elem, 0, &val);
162     if (max != 100) {
163         float avgf = ((float)val / max) * 100;
164         avg = (int)avgf;
165         avg = (avgf - avg < 0.5 ? avg : (avg + 1));
166     } else
167         avg = (int)val;
168
169     /* Check for mute */
170     if (snd_mixer_selem_has_playback_switch(elem)) {
171         if ((err = snd_mixer_selem_get_playback_switch(elem, 0, &pbval)) < 0)
172             fprintf(stderr, "i3status: ALSA: playback_switch: %s\n", snd_strerror(err));
173         if (!pbval) {
174             START_COLOR("color_degraded");
175             fmt = fmt_muted;
176         }
177     }
178
179     snd_mixer_close(m);
180     snd_mixer_selem_id_free(sid);
181
182     outwalk = apply_volume_format(fmt, outwalk, avg);
183
184 #endif
185 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
186     char *mixerpath;
187     char defaultmixer[] = "/dev/mixer";
188     int mixfd, vol, devmask = 0;
189     pbval = 1;
190
191     if (mixer_idx > 0)
192         asprintf(&mixerpath, "/dev/mixer%d", mixer_idx);
193     else
194         mixerpath = defaultmixer;
195
196     if ((mixfd = open(mixerpath, O_RDWR)) < 0) {
197 #if defined(__OpenBSD__)
198         warn("audioio: Cannot open mixer");
199 #else
200         warn("OSS: Cannot open mixer");
201 #endif
202         goto out;
203     }
204
205     if (mixer_idx > 0)
206         free(mixerpath);
207
208 #if defined(__OpenBSD__)
209     int oclass_idx = -1, master_idx = -1, master_mute_idx = -1;
210     mixer_devinfo_t devinfo, devinfo2;
211     mixer_ctrl_t vinfo;
212
213     devinfo.index = 0;
214     while (ioctl(mixfd, AUDIO_MIXER_DEVINFO, &devinfo) >= 0) {
215         if (devinfo.type != AUDIO_MIXER_CLASS) {
216             devinfo.index++;
217             continue;
218         }
219         if (strncmp(devinfo.label.name, AudioCoutputs, MAX_AUDIO_DEV_LEN) == 0)
220             oclass_idx = devinfo.index;
221
222         devinfo.index++;
223     }
224
225     devinfo2.index = 0;
226     while (ioctl(mixfd, AUDIO_MIXER_DEVINFO, &devinfo2) >= 0) {
227         if ((devinfo2.type == AUDIO_MIXER_VALUE) && (devinfo2.mixer_class == oclass_idx) && (strncmp(devinfo2.label.name, AudioNmaster, MAX_AUDIO_DEV_LEN) == 0))
228             master_idx = devinfo2.index;
229
230         if ((devinfo2.type == AUDIO_MIXER_ENUM) && (devinfo2.mixer_class == oclass_idx) && (strncmp(devinfo2.label.name, AudioNmute, MAX_AUDIO_DEV_LEN) == 0))
231             master_mute_idx = devinfo2.index;
232
233         devinfo2.index++;
234     }
235
236     if (master_idx == -1)
237         goto out;
238
239     devinfo.index = master_idx;
240     if (ioctl(mixfd, AUDIO_MIXER_DEVINFO, &devinfo) == -1)
241         goto out;
242
243     vinfo.dev = master_idx;
244     vinfo.type = AUDIO_MIXER_VALUE;
245     if (ioctl(mixfd, AUDIO_MIXER_READ, &vinfo) == -1)
246         goto out;
247
248     if (AUDIO_MAX_GAIN != 100) {
249         float avgf = ((float)vinfo.un.value.level[AUDIO_MIXER_LEVEL_MONO] / AUDIO_MAX_GAIN) * 100;
250         vol = (int)avgf;
251         vol = (avgf - vol < 0.5 ? vol : (vol + 1));
252     } else {
253         vol = (int)vinfo.un.value.level[AUDIO_MIXER_LEVEL_MONO];
254     }
255
256     vinfo.dev = master_mute_idx;
257     vinfo.type = AUDIO_MIXER_ENUM;
258     if (ioctl(mixfd, AUDIO_MIXER_READ, &vinfo) == -1)
259         goto out;
260
261     if (master_mute_idx != -1 && vinfo.un.ord) {
262         START_COLOR("color_degraded");
263         fmt = fmt_muted;
264         pbval = 0;
265     }
266
267 #else
268     if (ioctl(mixfd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) {
269         warn("OSS: Cannot read mixer information");
270         goto out;
271     }
272     if (ioctl(mixfd, MIXER_READ(0), &vol) == -1) {
273         warn("OSS: Cannot read mixer information");
274         goto out;
275     }
276
277     if (((vol & 0x7f) == 0) && (((vol >> 8) & 0x7f) == 0)) {
278         START_COLOR("color_degraded");
279         pbval = 0;
280     }
281
282 #endif
283     outwalk = apply_volume_format(fmt, outwalk, vol & 0x7f);
284     close(mixfd);
285 #endif
286
287 out:
288     *outwalk = '\0';
289     if (!pbval)
290         END_COLOR;
291     OUTPUT_FULL_TEXT(buffer);
292 }