1 /*        $NetBSD: mixerctl.c,v 1.30 2021/12/26 15:36:49 rillig Exp $ */
2 
3 /*
4  * Copyright (c) 1997 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Lennart Augustsson (augustss@NetBSD.org) and Chuck Cranor.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 #include <sys/cdefs.h>
32 
33 #ifndef lint
34 __RCSID("$NetBSD: mixerctl.c,v 1.30 2021/12/26 15:36:49 rillig Exp $");
35 #endif
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <fcntl.h>
40 #include <err.h>
41 #include <unistd.h>
42 #include <string.h>
43 #include <sys/types.h>
44 #include <sys/ioctl.h>
45 #include <sys/audioio.h>
46 
47 #include <paths.h>
48 
49 static FILE *out = stdout;
50 static int vflag = 0;
51 
52 static struct field {
53           char *name;
54           mixer_ctrl_t *valp;
55           mixer_devinfo_t *infp;
56           char changed;
57 } *fields, *rfields;
58 
59 static mixer_ctrl_t *values;
60 static mixer_devinfo_t *infos;
61 
62 static const char mixer_path[] = _PATH_MIXER;
63 
64 static char *
catstr(char * p,char * q)65 catstr(char *p, char *q)
66 {
67           char *r;
68 
69           asprintf(&r, "%s.%s", p, q);
70           if (!r)
71                     err(EXIT_FAILURE, "malloc");
72           return r;
73 }
74 
75 static struct field *
findfield(const char * name)76 findfield(const char *name)
77 {
78           int i;
79           for (i = 0; fields[i].name; i++)
80                     if (strcmp(fields[i].name, name) == 0)
81                               return &fields[i];
82           return 0;
83 }
84 
85 static void
prfield(struct field * p,const char * sep,int prvalset)86 prfield(struct field *p, const char *sep, int prvalset)
87 {
88           mixer_ctrl_t *m;
89           int i, n;
90 
91           if (sep)
92                     fprintf(out, "%s%s", p->name, sep);
93           m = p->valp;
94           switch(m->type) {
95           case AUDIO_MIXER_ENUM:
96                     for (i = 0; i < p->infp->un.e.num_mem; i++)
97                               if (p->infp->un.e.member[i].ord == m->un.ord)
98                                         fprintf(out, "%s",
99                                             p->infp->un.e.member[i].label.name);
100                     if (prvalset) {
101                               fprintf(out, "  [ ");
102                               for (i = 0; i < p->infp->un.e.num_mem; i++)
103                                         fprintf(out, "%s ",
104                                             p->infp->un.e.member[i].label.name);
105                               fprintf(out, "]");
106                     }
107                     break;
108           case AUDIO_MIXER_SET:
109                     for (n = i = 0; i < p->infp->un.s.num_mem; i++)
110                               if (m->un.mask & p->infp->un.s.member[i].mask)
111                                         fprintf(out, "%s%s", n++ ? "," : "",
112                                             p->infp->un.s.member[i].label.name);
113                     if (prvalset) {
114                               fprintf(out, "  { ");
115                               for (i = 0; i < p->infp->un.s.num_mem; i++)
116                                         fprintf(out, "%s ",
117                                             p->infp->un.s.member[i].label.name);
118                               fprintf(out, "}");
119                     }
120                     break;
121           case AUDIO_MIXER_VALUE:
122                     if (m->un.value.num_channels == 1)
123                               fprintf(out, "%d", m->un.value.level[0]);
124                     else
125                               fprintf(out, "%d,%d", m->un.value.level[0],
126                                   m->un.value.level[1]);
127                     if (prvalset) {
128                               fprintf(out, " %s", p->infp->un.v.units.name);
129                               if (p->infp->un.v.delta)
130                                         fprintf(out, " delta=%d", p->infp->un.v.delta);
131                     }
132                     break;
133           default:
134                     printf("\n");
135                     errx(EXIT_FAILURE, "Invalid format %d", m->type);
136           }
137 }
138 
139 static int
clip(int vol)140 clip(int vol)
141 {
142           if (vol <= AUDIO_MIN_GAIN)
143                     return AUDIO_MIN_GAIN;
144           if (vol >= AUDIO_MAX_GAIN)
145                     return AUDIO_MAX_GAIN;
146           return vol;
147 }
148 
149 static int
rdfield(struct field * p,char * q)150 rdfield(struct field *p, char *q)
151 {
152           mixer_ctrl_t *m;
153           int v, v0, v1, mask;
154           int i;
155           char *s;
156 
157           m = p->valp;
158           switch(m->type) {
159           case AUDIO_MIXER_ENUM:
160                     for (i = 0; i < p->infp->un.e.num_mem; i++)
161                               if (strcmp(p->infp->un.e.member[i].label.name, q) == 0)
162                                         break;
163                     if (i < p->infp->un.e.num_mem)
164                               m->un.ord = p->infp->un.e.member[i].ord;
165                     else {
166                               warnx("Bad enum value %s", q);
167                               return 0;
168                     }
169                     break;
170           case AUDIO_MIXER_SET:
171                     mask = 0;
172                     for (v = 0; q && *q; q = s) {
173                               s = strchr(q, ',');
174                               if (s)
175                                         *s++ = 0;
176                               for (i = 0; i < p->infp->un.s.num_mem; i++)
177                                         if (strcmp(p->infp->un.s.member[i].label.name,
178                                             q) == 0)
179                                                   break;
180                               if (i < p->infp->un.s.num_mem) {
181                                         mask |= p->infp->un.s.member[i].mask;
182                               } else {
183                                         warnx("Bad set value %s", q);
184                                         return 0;
185                               }
186                     }
187                     m->un.mask = mask;
188                     break;
189           case AUDIO_MIXER_VALUE:
190                     if (m->un.value.num_channels == 1) {
191                               if (sscanf(q, "%d", &v) == 1) {
192                                         m->un.value.level[0] = clip(v);
193                               } else {
194                                         warnx("Bad number %s", q);
195                                         return 0;
196                               }
197                     } else {
198                               if (sscanf(q, "%d,%d", &v0, &v1) == 2) {
199                                         m->un.value.level[0] = clip(v0);
200                                         m->un.value.level[1] = clip(v1);
201                               } else if (sscanf(q, "%d", &v) == 1) {
202                                         m->un.value.level[0] =
203                                             m->un.value.level[1] = clip(v);
204                               } else {
205                                         warnx("Bad numbers %s", q);
206                                         return 0;
207                               }
208                     }
209                     break;
210           default:
211                     errx(EXIT_FAILURE, "Invalid format %d", m->type);
212           }
213           p->changed = 1;
214           return 1;
215 }
216 
217 static int
incfield(struct field * p,int inc)218 incfield(struct field *p, int inc)
219 {
220           mixer_ctrl_t *m;
221           int i, v;
222 
223           m = p->valp;
224           switch(m->type) {
225           case AUDIO_MIXER_ENUM:
226                     m->un.ord += inc;
227                     if (m->un.ord < 0)
228                               m->un.ord = p->infp->un.e.num_mem - 1;
229                     if (m->un.ord >= p->infp->un.e.num_mem)
230                               m->un.ord = 0;
231                     break;
232           case AUDIO_MIXER_SET:
233                     m->un.mask += inc;
234                     if (m->un.mask < 0)
235                               m->un.mask = (1 << p->infp->un.s.num_mem) - 1;
236                     if (m->un.mask >= (1 << p->infp->un.s.num_mem))
237                               m->un.mask = 0;
238                     warnx("Can't ++/-- %s", p->name);
239                     return 0;
240           case AUDIO_MIXER_VALUE:
241                     if (p->infp->un.v.delta)
242                               inc *= p->infp->un.v.delta;
243                     for (i = 0; i < m->un.value.num_channels; i++) {
244                               v = m->un.value.level[i];
245                               v += inc;
246                               m->un.value.level[i] = clip(v);
247                     }
248                     break;
249           default:
250                     errx(EXIT_FAILURE, "Invalid format %d", m->type);
251           }
252           p->changed = 1;
253           return 1;
254 }
255 
256 static void
wrarg(int fd,char * arg,const char * sep)257 wrarg(int fd, char *arg, const char *sep)
258 {
259           char *q;
260           struct field *p;
261           mixer_ctrl_t val;
262           int incdec, r;
263 
264           q = strchr(arg, '=');
265           if (q == NULL) {
266                     int l = strlen(arg);
267                     incdec = 0;
268                     if (l > 2 && arg[l-2] == '+' && arg[l-1] == '+')
269                               incdec = 1;
270                     else if (l > 2 && arg[l-2] == '-' && arg[l-1] == '-')
271                               incdec = -1;
272                     else {
273                               warnx("No `=' in %s", arg);
274                               return;
275                     }
276                     arg[l-2] = 0;
277           } else if (q > arg && (*(q-1) == '+' || *(q-1) == '-')) {
278                     if (sscanf(q+1, "%d", &incdec) != 1) {
279                               warnx("Bad number %s", q+1);
280                               return;
281                     }
282                     if (*(q-1) == '-')
283                               incdec *= -1;
284                     *(q-1) = 0;
285                     q = NULL;
286           } else
287                     *q++ = 0;
288 
289           p = findfield(arg);
290           if (p == NULL) {
291                     warnx("field %s does not exist", arg);
292                     return;
293           }
294 
295           val = *p->valp;
296           if (q != NULL)
297                     r = rdfield(p, q);
298           else
299                     r = incfield(p, incdec);
300           if (r) {
301                     if (ioctl(fd, AUDIO_MIXER_WRITE, p->valp) == -1)
302                               warn("AUDIO_MIXER_WRITE");
303                     else if (sep) {
304                               *p->valp = val;
305                               prfield(p, ": ", 0);
306                               if (ioctl(fd, AUDIO_MIXER_READ, p->valp) == -1)
307                                         warn("AUDIO_MIXER_READ");
308                               printf(" -> ");
309                               prfield(p, 0, 0);
310                               printf("\n");
311                     }
312           }
313 }
314 
315 static void
prarg(int fd,char * arg,const char * sep)316 prarg(int fd, char *arg, const char *sep)
317 {
318           struct field *p;
319 
320           p = findfield(arg);
321           if (p == NULL)
322                     warnx("field %s does not exist", arg);
323           else
324                     prfield(p, sep, vflag), fprintf(out, "\n");
325 }
326 
327 static inline void __dead
usage(void)328 usage(void)
329 {
330           const char *prog = getprogname();
331 
332           fprintf(stderr, "Usage:\t%s [-d file] [-v] [-n] name ...\n", prog);
333           fprintf(stderr, "\t%s [-d file] [-v] [-n] -w name=value ...\n", prog);
334           fprintf(stderr, "\t%s [-d file] [-v] [-n] -a\n", prog);
335           exit(EXIT_FAILURE);
336 }
337 
338 int
main(int argc,char ** argv)339 main(int argc, char **argv)
340 {
341           int fd, i, j, ch, pos;
342           int aflag = 0, wflag = 0;
343           const char *file;
344           const char *sep = "=";
345           mixer_devinfo_t dinfo;
346           int ndev;
347 
348           file = getenv("MIXERDEVICE");
349           if (file == NULL)
350                     file = mixer_path;
351 
352 
353           while ((ch = getopt(argc, argv, "ad:f:nvw")) != -1) {
354                     switch(ch) {
355                     case 'a':
356                               aflag++;
357                               break;
358                     case 'w':
359                               wflag++;
360                               break;
361                     case 'v':
362                               vflag++;
363                               break;
364                     case 'n':
365                               sep = 0;
366                               break;
367                     case 'f': /* compatibility */
368                     case 'd':
369                               file = optarg;
370                               break;
371                     case '?':
372                     default:
373                               usage();
374                     }
375           }
376           argc -= optind;
377           argv += optind;
378 
379           if (aflag ? (argc != 0 || wflag) : argc == 0)
380                     usage();
381 
382           fd = open(file, O_RDWR);
383           /* Try with mixer0 but only if using the default device. */
384           if (fd == -1 && file == mixer_path) {
385                     file = _PATH_MIXER0;
386                     fd = open(file, O_RDWR);
387           }
388 
389           if (fd == -1)
390                     err(EXIT_FAILURE, "Can't open `%s'", file);
391 
392           for (ndev = 0; ; ndev++) {
393                     dinfo.index = ndev;
394                     if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) == -1)
395                               break;
396           }
397           rfields = calloc(ndev, sizeof *rfields);
398           fields = calloc(ndev, sizeof *fields);
399           infos = calloc(ndev, sizeof *infos);
400           values = calloc(ndev, sizeof *values);
401 
402           for (i = 0; i < ndev; i++) {
403                     infos[i].index = i;
404                     if (ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]) == -1)
405                               warn("AUDIO_MIXER_DEVINFO for %d", i);
406           }
407 
408           for (i = 0; i < ndev; i++) {
409                     rfields[i].name = infos[i].label.name;
410                     rfields[i].valp = &values[i];
411                     rfields[i].infp = &infos[i];
412           }
413 
414           for (i = 0; i < ndev; i++) {
415                     values[i].dev = i;
416                     values[i].type = infos[i].type;
417                     if (infos[i].type != AUDIO_MIXER_CLASS) {
418                               values[i].un.value.num_channels = 2;
419                               if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) == -1) {
420                                         values[i].un.value.num_channels = 1;
421                                         if (ioctl(fd, AUDIO_MIXER_READ, &values[i])
422                                             == -1)
423                                                   err(EXIT_FAILURE, "AUDIO_MIXER_READ");
424                               }
425                     }
426           }
427 
428           for (j = i = 0; i < ndev; i++) {
429                     if (infos[i].type != AUDIO_MIXER_CLASS &&
430                         infos[i].type != -1) {
431                               fields[j++] = rfields[i];
432                               for (pos = infos[i].next; pos != AUDIO_MIXER_LAST;
433                                   pos = infos[pos].next) {
434                                         fields[j] = rfields[pos];
435                                         fields[j].name = catstr(rfields[i].name,
436                                             infos[pos].label.name);
437                                         infos[pos].type = -1;
438                                         j++;
439                               }
440                     }
441           }
442 
443           for (i = 0; i < j; i++) {
444                     int cls = fields[i].infp->mixer_class;
445                     if (cls >= 0 && cls < ndev)
446                               fields[i].name = catstr(infos[cls].label.name,
447                                   fields[i].name);
448           }
449 
450           if (argc == 0 && aflag && !wflag) {
451                     for (i = 0; i < j; i++) {
452                               prfield(&fields[i], sep, vflag);
453                               fprintf(out, "\n");
454                     }
455           } else if (argc > 0 && !aflag) {
456                     while (argc--) {
457                               if (wflag)
458                                         wrarg(fd, *argv, sep);
459                               else
460                                         prarg(fd, *argv, sep);
461                               argv++;
462                     }
463           } else
464                     usage();
465           return EXIT_SUCCESS;
466 }
467