]> git.sur5r.net Git - i3/i3status/blob - src/print_volume.c
4359ac10eb185c8c5badda791266c286ded9bb4c
[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 ivolume = pulse_initialize() ? volume_pulseaudio(sink_idx) : 0;
71         /* negative result means error, stick to 0 */
72         if (ivolume < 0)
73             ivolume = 0;
74         outwalk = apply_volume_format(fmt, outwalk, ivolume);
75         goto out;
76     } else if (!strcasecmp(device, "default") && pulse_initialize()) {
77         /* no device specified or "default" set */
78         int ivolume = volume_pulseaudio(DEFAULT_SINK_INDEX);
79         if (ivolume >= 0) {
80             outwalk = apply_volume_format(fmt, outwalk, ivolume);
81             goto out;
82         }
83         /* negative result means error, fail PulseAudio attempt */
84     }
85 /* If some other device was specified or PulseAudio is not detected,
86  * proceed to ALSA / OSS */
87
88 #ifdef LINUX
89     int err;
90     snd_mixer_t *m;
91     snd_mixer_selem_id_t *sid;
92     snd_mixer_elem_t *elem;
93     long min, max, val;
94     int avg;
95
96     if ((err = snd_mixer_open(&m, 0)) < 0) {
97         fprintf(stderr, "i3status: ALSA: Cannot open mixer: %s\n", snd_strerror(err));
98         goto out;
99     }
100
101     /* Attach this mixer handle to the given device */
102     if ((err = snd_mixer_attach(m, device)) < 0) {
103         fprintf(stderr, "i3status: ALSA: Cannot attach mixer to device: %s\n", snd_strerror(err));
104         snd_mixer_close(m);
105         goto out;
106     }
107
108     /* Register this mixer */
109     if ((err = snd_mixer_selem_register(m, NULL, NULL)) < 0) {
110         fprintf(stderr, "i3status: ALSA: snd_mixer_selem_register: %s\n", snd_strerror(err));
111         snd_mixer_close(m);
112         goto out;
113     }
114
115     if ((err = snd_mixer_load(m)) < 0) {
116         fprintf(stderr, "i3status: ALSA: snd_mixer_load: %s\n", snd_strerror(err));
117         snd_mixer_close(m);
118         goto out;
119     }
120
121     snd_mixer_selem_id_malloc(&sid);
122     if (sid == NULL) {
123         snd_mixer_close(m);
124         goto out;
125     }
126
127     /* Find the given mixer */
128     snd_mixer_selem_id_set_index(sid, mixer_idx);
129     snd_mixer_selem_id_set_name(sid, mixer);
130     if (!(elem = snd_mixer_find_selem(m, sid))) {
131         fprintf(stderr, "i3status: ALSA: Cannot find mixer %s (index %i)\n",
132                 snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
133         snd_mixer_close(m);
134         snd_mixer_selem_id_free(sid);
135         goto out;
136     }
137
138     /* Get the volume range to convert the volume later */
139     snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
140
141     snd_mixer_handle_events(m);
142     snd_mixer_selem_get_playback_volume(elem, 0, &val);
143     if (max != 100) {
144         float avgf = ((float)val / max) * 100;
145         avg = (int)avgf;
146         avg = (avgf - avg < 0.5 ? avg : (avg + 1));
147     } else
148         avg = (int)val;
149
150     /* Check for mute */
151     if (snd_mixer_selem_has_playback_switch(elem)) {
152         if ((err = snd_mixer_selem_get_playback_switch(elem, 0, &pbval)) < 0)
153             fprintf(stderr, "i3status: ALSA: playback_switch: %s\n", snd_strerror(err));
154         if (!pbval) {
155             START_COLOR("color_degraded");
156             fmt = fmt_muted;
157         }
158     }
159
160     snd_mixer_close(m);
161     snd_mixer_selem_id_free(sid);
162
163     outwalk = apply_volume_format(fmt, outwalk, avg);
164
165 #endif
166 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
167     char *mixerpath;
168     char defaultmixer[] = "/dev/mixer";
169     int mixfd, vol, devmask = 0;
170     pbval = 1;
171
172     if (mixer_idx > 0)
173         asprintf(&mixerpath, "/dev/mixer%d", mixer_idx);
174     else
175         mixerpath = defaultmixer;
176
177     if ((mixfd = open(mixerpath, O_RDWR)) < 0) {
178         warn("OSS: Cannot open mixer");
179         goto out;
180     }
181
182     if (mixer_idx > 0)
183         free(mixerpath);
184
185     if (ioctl(mixfd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) {
186         warn("OSS: Cannot read mixer information");
187         goto out;
188     }
189     if (ioctl(mixfd, MIXER_READ(0), &vol) == -1) {
190         warn("OSS: Cannot read mixer information");
191         goto out;
192     }
193
194     if (((vol & 0x7f) == 0) && (((vol >> 8) & 0x7f) == 0)) {
195         START_COLOR("color_degraded");
196         pbval = 0;
197     }
198
199     outwalk = apply_volume_format(fmt, outwalk, vol & 0x7f);
200     close(mixfd);
201 #endif
202
203 out:
204     *outwalk = '\0';
205     if (!pbval)
206         END_COLOR;
207     OUTPUT_FULL_TEXT(buffer);
208 }