Xinqi Bao's Git

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