volume.c (4319B) download
1/* See LICENSE file for copyright and license details. */
2#include <fcntl.h>
3#include <stdio.h>
4#include <string.h>
5#include <sys/ioctl.h>
6#include <unistd.h>
7
8#include "../slstatus.h"
9#include "../util.h"
10
11#if defined(__OpenBSD__) | defined(__FreeBSD__)
12 #include <poll.h>
13 #include <sndio.h>
14 #include <stdlib.h>
15 #include <sys/queue.h>
16
17 struct control {
18 LIST_ENTRY(control) next;
19 unsigned int addr;
20 #define CTRL_NONE 0
21 #define CTRL_LEVEL 1
22 #define CTRL_MUTE 2
23 unsigned int type;
24 unsigned int maxval;
25 unsigned int val;
26 };
27
28 static LIST_HEAD(, control) controls = LIST_HEAD_INITIALIZER(controls);
29 static struct pollfd *pfds;
30 static struct sioctl_hdl *hdl;
31 static int initialized;
32
33 /*
34 * Call-back to obtain the description of all audio controls.
35 */
36 static void
37 ondesc(void *unused, struct sioctl_desc *desc, int val)
38 {
39 struct control *c, *ctmp;
40 unsigned int type = CTRL_NONE;
41
42 if (desc == NULL)
43 return;
44
45 /* Delete existing audio control with the same address. */
46 LIST_FOREACH_SAFE(c, &controls, next, ctmp) {
47 if (desc->addr == c->addr) {
48 LIST_REMOVE(c, next);
49 free(c);
50 break;
51 }
52 }
53
54 /* Only match output.level and output.mute audio controls. */
55 if (desc->group[0] != 0 ||
56 strcmp(desc->node0.name, "output") != 0)
57 return;
58 if (desc->type == SIOCTL_NUM &&
59 strcmp(desc->func, "level") == 0)
60 type = CTRL_LEVEL;
61 else if (desc->type == SIOCTL_SW &&
62 strcmp(desc->func, "mute") == 0)
63 type = CTRL_MUTE;
64 else
65 return;
66
67 c = malloc(sizeof(struct control));
68 if (c == NULL) {
69 warn("sndio: failed to allocate audio control\n");
70 return;
71 }
72
73 c->addr = desc->addr;
74 c->type = type;
75 c->maxval = desc->maxval;
76 c->val = val;
77 LIST_INSERT_HEAD(&controls, c, next);
78 }
79
80 /*
81 * Call-back invoked whenever an audio control changes.
82 */
83 static void
84 onval(void *unused, unsigned int addr, unsigned int val)
85 {
86 struct control *c;
87
88 LIST_FOREACH(c, &controls, next) {
89 if (c->addr == addr)
90 break;
91 }
92 c->val = val;
93 }
94
95 static void
96 cleanup(void)
97 {
98 struct control *c;
99
100 if (hdl) {
101 sioctl_close(hdl);
102 hdl = NULL;
103 }
104
105 free(pfds);
106 pfds = NULL;
107
108 while (!LIST_EMPTY(&controls)) {
109 c = LIST_FIRST(&controls);
110 LIST_REMOVE(c, next);
111 free(c);
112 }
113 }
114
115 static int
116 init(void)
117 {
118 hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0);
119 if (hdl == NULL) {
120 warn("sndio: cannot open device");
121 goto failed;
122 }
123
124 if (!sioctl_ondesc(hdl, ondesc, NULL)) {
125 warn("sndio: cannot set control description call-back");
126 goto failed;
127 }
128
129 if (!sioctl_onval(hdl, onval, NULL)) {
130 warn("sndio: cannot set control values call-back");
131 goto failed;
132 }
133
134 pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd));
135 if (pfds == NULL) {
136 warn("sndio: cannot allocate pollfd structures");
137 goto failed;
138 }
139
140 return 1;
141 failed:
142 cleanup();
143 return 0;
144 }
145
146 const char *
147 vol_perc(const char *unused)
148 {
149 struct control *c;
150 int n, v, value;
151
152 if (!initialized)
153 initialized = init();
154
155 if (hdl == NULL)
156 return NULL;
157
158 n = sioctl_pollfd(hdl, pfds, POLLIN);
159 if (n > 0) {
160 n = poll(pfds, n, 0);
161 if (n > 0) {
162 if (sioctl_revents(hdl, pfds) & POLLHUP) {
163 warn("sndio: disconnected");
164 cleanup();
165 initialized = 0;
166 return NULL;
167 }
168 }
169 }
170
171 value = 100;
172 LIST_FOREACH(c, &controls, next) {
173 if (c->type == CTRL_MUTE && c->val == 1)
174 value = 0;
175 else if (c->type == CTRL_LEVEL) {
176 v = (c->val * 100 + c->maxval / 2) / c->maxval;
177 /* For multiple channels return the minimum. */
178 if (v < value)
179 value = v;
180 }
181 }
182
183 return bprintf("%d", value);
184 }
185#else
186 #include <sys/soundcard.h>
187
188 const char *
189 vol_perc(const char *card)
190 {
191 size_t i;
192 int v, afd, devmask;
193 char *vnames[] = SOUND_DEVICE_NAMES;
194
195 if ((afd = open(card, O_RDONLY | O_NONBLOCK)) < 0) {
196 warn("open '%s':", card);
197 return NULL;
198 }
199
200 if (ioctl(afd, (int)SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
201 warn("ioctl 'SOUND_MIXER_READ_DEVMASK':");
202 close(afd);
203 return NULL;
204 }
205 for (i = 0; i < LEN(vnames); i++) {
206 if (devmask & (1 << i) && !strcmp("vol", vnames[i])) {
207 if (ioctl(afd, MIXER_READ(i), &v) < 0) {
208 warn("ioctl 'MIXER_READ(%ld)':", i);
209 close(afd);
210 return NULL;
211 }
212 }
213 }
214
215 close(afd);
216
217 return bprintf("%d", v & 0xff);
218 }
219#endif