]> git.sur5r.net Git - i3/i3status/blob - src/print_volume.c
ef1c913212b5eda50f96d33fb704649555996b0b
[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 <yajl/yajl_gen.h>
7 #include <yajl/yajl_version.h>
8
9 #ifdef LINUX
10 #include <alsa/asoundlib.h>
11 #include <alloca.h>
12 #endif
13
14 #if defined(__FreeBSD__) || defined(__DragonFly__)
15 #include <fcntl.h>
16 #include <unistd.h>
17 #include <sys/soundcard.h>
18 #endif
19
20 #ifdef __OpenBSD__
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <soundcard.h>
24 #endif
25
26 #include "i3status.h"
27 #include "queue.h"
28
29 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) {
30     char *outwalk = buffer;
31     int pbval = 1;
32
33     /* Printing volume only works with ALSA at the moment */
34     if (output_format == O_I3BAR) {
35         char *instance;
36         asprintf(&instance, "%s.%s.%d", device, mixer, mixer_idx);
37         INSTANCE(instance);
38         free(instance);
39     }
40 #ifdef LINUX
41     int err;
42     snd_mixer_t *m;
43     snd_mixer_selem_id_t *sid;
44     snd_mixer_elem_t *elem;
45     long min, max, val;
46     int avg;
47
48     if ((err = snd_mixer_open(&m, 0)) < 0) {
49         fprintf(stderr, "i3status: ALSA: Cannot open mixer: %s\n", snd_strerror(err));
50         goto out;
51     }
52
53     /* Attach this mixer handle to the given device */
54     if ((err = snd_mixer_attach(m, device)) < 0) {
55         fprintf(stderr, "i3status: ALSA: Cannot attach mixer to device: %s\n", snd_strerror(err));
56         snd_mixer_close(m);
57         goto out;
58     }
59
60     /* Register this mixer */
61     if ((err = snd_mixer_selem_register(m, NULL, NULL)) < 0) {
62         fprintf(stderr, "i3status: ALSA: snd_mixer_selem_register: %s\n", snd_strerror(err));
63         snd_mixer_close(m);
64         goto out;
65     }
66
67     if ((err = snd_mixer_load(m)) < 0) {
68         fprintf(stderr, "i3status: ALSA: snd_mixer_load: %s\n", snd_strerror(err));
69         snd_mixer_close(m);
70         goto out;
71     }
72
73     snd_mixer_selem_id_malloc(&sid);
74     if (sid == NULL) {
75         snd_mixer_close(m);
76         goto out;
77     }
78
79     /* Find the given mixer */
80     snd_mixer_selem_id_set_index(sid, mixer_idx);
81     snd_mixer_selem_id_set_name(sid, mixer);
82     if (!(elem = snd_mixer_find_selem(m, sid))) {
83         fprintf(stderr, "i3status: ALSA: Cannot find mixer %s (index %i)\n",
84                 snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
85         snd_mixer_close(m);
86         snd_mixer_selem_id_free(sid);
87         goto out;
88     }
89
90     /* Get the volume range to convert the volume later */
91     snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
92
93     snd_mixer_handle_events(m);
94     snd_mixer_selem_get_playback_volume(elem, 0, &val);
95     if (max != 100) {
96         float avgf = ((float)val / max) * 100;
97         avg = (int)avgf;
98         avg = (avgf - avg < 0.5 ? avg : (avg + 1));
99     } else
100         avg = (int)val;
101
102     /* Check for mute */
103     if (snd_mixer_selem_has_playback_switch(elem)) {
104         if ((err = snd_mixer_selem_get_playback_switch(elem, 0, &pbval)) < 0)
105             fprintf(stderr, "i3status: ALSA: playback_switch: %s\n", snd_strerror(err));
106         if (!pbval) {
107             START_COLOR("color_degraded");
108             fmt = fmt_muted;
109         }
110     }
111
112     snd_mixer_close(m);
113     snd_mixer_selem_id_free(sid);
114
115     const char *walk = fmt;
116     for (; *walk != '\0'; walk++) {
117         if (*walk != '%') {
118             *(outwalk++) = *walk;
119             continue;
120         }
121         if (BEGINS_WITH(walk + 1, "%")) {
122             outwalk += sprintf(outwalk, "%%");
123             walk += strlen("%");
124         }
125         if (BEGINS_WITH(walk + 1, "volume")) {
126             outwalk += sprintf(outwalk, "%d%%", avg);
127             walk += strlen("volume");
128         }
129     }
130 #endif
131 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
132     char *mixerpath;
133     char defaultmixer[] = "/dev/mixer";
134     int mixfd, vol, devmask = 0;
135     pbval = 1;
136
137     if (mixer_idx > 0)
138         asprintf(&mixerpath, "/dev/mixer%d", mixer_idx);
139     else
140         mixerpath = defaultmixer;
141
142     if ((mixfd = open(mixerpath, O_RDWR)) < 0)
143         return;
144
145     if (mixer_idx > 0)
146         free(mixerpath);
147
148     if (ioctl(mixfd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
149         return;
150     if (ioctl(mixfd, MIXER_READ(0), &vol) == -1)
151         return;
152
153     if (((vol & 0x7f) == 0) && (((vol >> 8) & 0x7f) == 0)) {
154         START_COLOR("color_degraded");
155         pbval = 0;
156     }
157
158     const char *walk = fmt;
159     for (; *walk != '\0'; walk++) {
160         if (*walk != '%') {
161             *(outwalk++) = *walk;
162             continue;
163         }
164         if (BEGINS_WITH(walk + 1, "%")) {
165             outwalk += sprintf(outwalk, "%%");
166             walk += strlen("%");
167         }
168         if (BEGINS_WITH(walk + 1, "volume")) {
169             outwalk += sprintf(outwalk, "%d%%", vol & 0x7f);
170             walk += strlen("volume");
171         }
172     }
173     close(mixfd);
174 #endif
175
176 out:
177     *outwalk = '\0';
178     if (!pbval)
179         END_COLOR;
180     OUTPUT_FULL_TEXT(buffer);
181 }