main.c revision 1.7 1 /* $NetBSD: main.c,v 1.7 2024/11/03 10:43:27 rillig Exp $ */
2 /*-
3 * Copyright (c) 2021 The NetBSD Foundation, Inc.
4 * All rights reserved.
5 *
6 * This code is derived from software contributed to The NetBSD Foundation
7 * by Nia Alarie.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
22 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30 #include <sys/audioio.h>
31 #include <sys/ioctl.h>
32 #include <fcntl.h>
33 #include <unistd.h>
34 #include <signal.h>
35 #include <paths.h>
36 #include <curses.h>
37 #include <stdlib.h>
38 #include <err.h>
39 #include "app.h"
40 #include "draw.h"
41 #include "parse.h"
42
43 static void process_device_select(struct aiomixer *, unsigned int);
44 static void open_device(struct aiomixer *, const char *);
45 static void __dead usage(void);
46 static int adjust_level(int, int);
47 static int select_class(struct aiomixer *, unsigned int);
48 static int select_control(struct aiomixer *, unsigned int);
49 static void slide_control(struct aiomixer *, struct aiomixer_control *, bool);
50 static int toggle_set(struct aiomixer *);
51 static void step_up(struct aiomixer *);
52 static void step_down(struct aiomixer *);
53 static int read_key(struct aiomixer *, int);
54
55 static void __dead
56 usage(void)
57 {
58 fputs("aiomixer [-u] [-d device]\n", stderr);
59 exit(1);
60 }
61
62 static int
63 select_class(struct aiomixer *aio, unsigned int n)
64 {
65 struct aiomixer_class *class;
66 unsigned i;
67
68 if (n >= aio->numclasses)
69 return -1;
70
71 class = &aio->classes[n];
72 aio->widgets_resized = true;
73 aio->class_scroll_y = 0;
74 aio->curcontrol = 0;
75 aio->curclass = n;
76 for (i = 0; i < class->numcontrols; ++i) {
77 class->controls[i].setindex = -1;
78 draw_control(aio, &class->controls[i], false);
79 }
80 draw_classbar(aio);
81 return 0;
82 }
83
84 static int
85 select_control(struct aiomixer *aio, unsigned int n)
86 {
87 struct aiomixer_class *class;
88 struct aiomixer_control *lastcontrol;
89 struct aiomixer_control *control;
90
91 class = &aio->classes[aio->curclass];
92
93 if (n >= class->numcontrols)
94 return -1;
95
96 lastcontrol = &class->controls[aio->curcontrol];
97 lastcontrol->setindex = -1;
98 draw_control(aio, lastcontrol, false);
99
100 control = &class->controls[n];
101 aio->curcontrol = n;
102 control->setindex = 0;
103 draw_control(aio, control, true);
104
105 if (aio->class_scroll_y > control->widget_y) {
106 aio->class_scroll_y = control->widget_y;
107 aio->widgets_resized = true;
108 }
109
110 if ((control->widget_y + control->height) >
111 ((getmaxy(stdscr) - 4) + aio->class_scroll_y)) {
112 aio->class_scroll_y = control->widget_y;
113 aio->widgets_resized = true;
114 }
115 return 0;
116 }
117
118 static int
119 adjust_level(int level, int delta)
120 {
121 if (level > (AUDIO_MAX_GAIN - delta))
122 return AUDIO_MAX_GAIN;
123
124 if (delta < 0 && level < (AUDIO_MIN_GAIN + (-delta)))
125 return AUDIO_MIN_GAIN;
126
127 return level + delta;
128 }
129
130 static void
131 slide_control(struct aiomixer *aio,
132 struct aiomixer_control *control, bool right)
133 {
134 struct mixer_devinfo *info = &control->info;
135 struct mixer_ctrl value;
136 unsigned char *level;
137 int i, delta;
138 int cur_index = 0;
139
140 if (info->type != AUDIO_MIXER_SET) {
141 value.dev = info->index;
142 value.type = info->type;
143 if (info->type == AUDIO_MIXER_VALUE)
144 value.un.value.num_channels = info->un.v.num_channels;
145
146 if (ioctl(aio->fd, AUDIO_MIXER_READ, &value) < 0)
147 err(EXIT_FAILURE, "failed to read mixer control");
148 }
149
150 switch (info->type) {
151 case AUDIO_MIXER_VALUE:
152 if (info->un.v.delta != 0) {
153 delta = right ? info->un.v.delta : -info->un.v.delta;
154 } else {
155 /* delta is 0 in qemu with sb(4) */
156 delta = right ? 16 : -16;
157 }
158 /*
159 * work around strange problem where the level can be
160 * increased but not decreased, seen with uaudio(4)
161 */
162 if (delta < 16)
163 delta *= 2;
164 if (aio->channels_unlocked) {
165 level = &value.un.value.level[control->setindex];
166 *level = (unsigned char)adjust_level(*level, delta);
167 } else {
168 for (i = 0; i < value.un.value.num_channels; ++i) {
169 level = &value.un.value.level[i];
170 *level = (unsigned char)adjust_level(*level, delta);
171 }
172 }
173 break;
174 case AUDIO_MIXER_ENUM:
175 for (i = 0; i < info->un.e.num_mem; ++i) {
176 if (info->un.e.member[i].ord == value.un.ord) {
177 cur_index = i;
178 break;
179 }
180 }
181 if (right) {
182 value.un.ord = cur_index < (info->un.e.num_mem - 1) ?
183 info->un.e.member[cur_index + 1].ord :
184 info->un.e.member[0].ord;
185 } else {
186 value.un.ord = cur_index > 0 ?
187 info->un.e.member[cur_index - 1].ord :
188 info->un.e.member[control->info.un.e.num_mem - 1].ord;
189 }
190 break;
191 case AUDIO_MIXER_SET:
192 if (right) {
193 control->setindex =
194 control->setindex < (info->un.s.num_mem - 1) ?
195 control->setindex + 1 : 0;
196 } else {
197 control->setindex = control->setindex > 0 ?
198 control->setindex - 1 :
199 control->info.un.s.num_mem - 1;
200 }
201 break;
202 }
203
204 if (info->type != AUDIO_MIXER_SET) {
205 if (ioctl(aio->fd, AUDIO_MIXER_WRITE, &value) < 0)
206 err(EXIT_FAILURE, "failed to adjust mixer control");
207 }
208
209 draw_control(aio, control, true);
210 }
211
212 static int
213 toggle_set(struct aiomixer *aio)
214 {
215 struct mixer_ctrl ctrl;
216 struct aiomixer_class *class = &aio->classes[aio->curclass];
217 struct aiomixer_control *control = &class->controls[aio->curcontrol];
218
219 ctrl.dev = control->info.index;
220 ctrl.type = control->info.type;
221
222 if (control->info.type != AUDIO_MIXER_SET)
223 return -1;
224
225 if (ioctl(aio->fd, AUDIO_MIXER_READ, &ctrl) < 0)
226 err(EXIT_FAILURE, "failed to read mixer control");
227
228 ctrl.un.mask ^= control->info.un.s.member[control->setindex].mask;
229
230 if (ioctl(aio->fd, AUDIO_MIXER_WRITE, &ctrl) < 0)
231 err(EXIT_FAILURE, "failed to read mixer control");
232
233 draw_control(aio, control, true);
234 return 0;
235 }
236
237 static void
238 step_up(struct aiomixer *aio)
239 {
240 struct aiomixer_class *class;
241 struct aiomixer_control *control;
242
243 class = &aio->classes[aio->curclass];
244 control = &class->controls[aio->curcontrol];
245
246 if (aio->channels_unlocked &&
247 control->info.type == AUDIO_MIXER_VALUE &&
248 control->setindex > 0) {
249 control->setindex--;
250 draw_control(aio, control, true);
251 return;
252 }
253 select_control(aio, aio->curcontrol - 1);
254 }
255
256 static void
257 step_down(struct aiomixer *aio)
258 {
259 struct aiomixer_class *class;
260 struct aiomixer_control *control;
261
262 class = &aio->classes[aio->curclass];
263 control = &class->controls[aio->curcontrol];
264
265 if (aio->channels_unlocked &&
266 control->info.type == AUDIO_MIXER_VALUE &&
267 control->setindex < (control->info.un.v.num_channels - 1)) {
268 control->setindex++;
269 draw_control(aio, control, true);
270 return;
271 }
272
273 select_control(aio, (aio->curcontrol + 1) % class->numcontrols);
274 }
275
276 static int
277 read_key(struct aiomixer *aio, int ch)
278 {
279 struct aiomixer_class *class;
280 struct aiomixer_control *control;
281 size_t i;
282
283 switch (ch) {
284 case KEY_RESIZE:
285 class = &aio->classes[aio->curclass];
286 resize_widgets(aio);
287 draw_header(aio);
288 draw_classbar(aio);
289 for (i = 0; i < class->numcontrols; ++i) {
290 draw_control(aio,
291 &class->controls[i],
292 aio->state == STATE_CONTROL_SELECT ?
293 (aio->curcontrol == i) : false);
294 }
295 break;
296 case KEY_LEFT:
297 case 'h':
298 if (aio->state == STATE_CLASS_SELECT) {
299 select_class(aio, aio->curclass > 0 ?
300 aio->curclass - 1 : aio->numclasses - 1);
301 } else if (aio->state == STATE_CONTROL_SELECT) {
302 class = &aio->classes[aio->curclass];
303 slide_control(aio,
304 &class->controls[aio->curcontrol], false);
305 }
306 break;
307 case KEY_RIGHT:
308 case 'l':
309 if (aio->state == STATE_CLASS_SELECT) {
310 select_class(aio,
311 (aio->curclass + 1) % aio->numclasses);
312 } else if (aio->state == STATE_CONTROL_SELECT) {
313 class = &aio->classes[aio->curclass];
314 slide_control(aio,
315 &class->controls[aio->curcontrol], true);
316 }
317 break;
318 case KEY_UP:
319 case 'k':
320 if (aio->state == STATE_CONTROL_SELECT) {
321 if (aio->curcontrol == 0) {
322 class = &aio->classes[aio->curclass];
323 control = &class->controls[aio->curcontrol];
324 control->setindex = -1;
325 aio->state = STATE_CLASS_SELECT;
326 draw_control(aio, control, false);
327 } else {
328 step_up(aio);
329 }
330 }
331 break;
332 case KEY_DOWN:
333 case 'j':
334 if (aio->state == STATE_CLASS_SELECT) {
335 class = &aio->classes[aio->curclass];
336 if (class->numcontrols > 0) {
337 aio->state = STATE_CONTROL_SELECT;
338 select_control(aio, 0);
339 }
340 } else if (aio->state == STATE_CONTROL_SELECT) {
341 step_down(aio);
342 }
343 break;
344 case '\n':
345 case ' ':
346 if (aio->state == STATE_CONTROL_SELECT)
347 toggle_set(aio);
348 break;
349 case '1':
350 select_class(aio, 0);
351 break;
352 case '2':
353 select_class(aio, 1);
354 break;
355 case '3':
356 select_class(aio, 2);
357 break;
358 case '4':
359 select_class(aio, 3);
360 break;
361 case '5':
362 select_class(aio, 4);
363 break;
364 case '6':
365 select_class(aio, 5);
366 break;
367 case '7':
368 select_class(aio, 6);
369 break;
370 case '8':
371 select_class(aio, 7);
372 break;
373 case '9':
374 select_class(aio, 8);
375 break;
376 case 'q':
377 case '\e':
378 if (aio->state == STATE_CONTROL_SELECT) {
379 class = &aio->classes[aio->curclass];
380 control = &class->controls[aio->curcontrol];
381 aio->state = STATE_CLASS_SELECT;
382 draw_control(aio, control, false);
383 break;
384 }
385 return 1;
386 case 'u':
387 aio->channels_unlocked = !aio->channels_unlocked;
388 if (aio->state == STATE_CONTROL_SELECT) {
389 class = &aio->classes[aio->curclass];
390 control = &class->controls[aio->curcontrol];
391 if (control->info.type == AUDIO_MIXER_VALUE)
392 draw_control(aio, control, true);
393 }
394 break;
395 }
396
397 draw_screen(aio);
398 return 0;
399 }
400
401 static void
402 process_device_select(struct aiomixer *aio, unsigned int num_devices)
403 {
404 unsigned int selected_device = 0;
405 char device_path[16];
406 int ch;
407
408 draw_mixer_select(num_devices, selected_device);
409
410 while ((ch = getch()) != ERR) {
411 switch (ch) {
412 case '\n':
413 clear();
414 (void)snprintf(device_path, sizeof(device_path),
415 "/dev/mixer%d", selected_device);
416 open_device(aio, device_path);
417 return;
418 case KEY_UP:
419 case 'k':
420 if (selected_device > 0)
421 selected_device--;
422 else
423 selected_device = (num_devices - 1);
424 break;
425 case KEY_DOWN:
426 case 'j':
427 if (selected_device < (num_devices - 1))
428 selected_device++;
429 else
430 selected_device = 0;
431 break;
432 case '1':
433 selected_device = 0;
434 break;
435 case '2':
436 selected_device = 1;
437 break;
438 case '3':
439 selected_device = 2;
440 break;
441 case '4':
442 selected_device = 3;
443 break;
444 case '5':
445 selected_device = 4;
446 break;
447 case '6':
448 selected_device = 5;
449 break;
450 case '7':
451 selected_device = 6;
452 break;
453 case '8':
454 selected_device = 7;
455 break;
456 case '9':
457 selected_device = 8;
458 break;
459 }
460 draw_mixer_select(num_devices, selected_device);
461 }
462 }
463
464 static void
465 open_device(struct aiomixer *aio, const char *device)
466 {
467 int ch;
468
469 if ((aio->fd = open(device, O_RDWR)) < 0)
470 err(EXIT_FAILURE, "couldn't open mixer device");
471
472 if (ioctl(aio->fd, AUDIO_GETDEV, &aio->mixerdev) < 0)
473 err(EXIT_FAILURE, "AUDIO_GETDEV failed");
474
475 aio->state = STATE_CLASS_SELECT;
476
477 aiomixer_parse(aio);
478
479 create_widgets(aio);
480
481 draw_header(aio);
482 select_class(aio, 0);
483 draw_screen(aio);
484
485 while ((ch = getch()) != ERR) {
486 if (read_key(aio, ch) != 0)
487 break;
488 }
489 }
490
491 static __dead void
492 on_signal(int dummy)
493 {
494 endwin();
495 exit(0);
496 }
497
498 int
499 main(int argc, char **argv)
500 {
501 const char *mixer_device = NULL;
502 struct aiomixer *aio;
503 char mixer_path[32];
504 unsigned int mixer_count = 0;
505 int i, fd;
506 int ch;
507 char *no_color = getenv("NO_COLOR");
508
509 if ((aio = malloc(sizeof(struct aiomixer))) == NULL) {
510 err(EXIT_FAILURE, "malloc failed");
511 }
512
513 while ((ch = getopt(argc, argv, "d:u")) != -1) {
514 switch (ch) {
515 case 'd':
516 mixer_device = optarg;
517 break;
518 case 'u':
519 aio->channels_unlocked = true;
520 break;
521 default:
522 usage();
523 break;
524 }
525 }
526
527 argc -= optind;
528 argv += optind;
529
530 if (initscr() == NULL)
531 err(EXIT_FAILURE, "can't initialize curses");
532
533 (void)signal(SIGHUP, on_signal);
534 (void)signal(SIGINT, on_signal);
535 (void)signal(SIGTERM, on_signal);
536
537 curs_set(0);
538 keypad(stdscr, TRUE);
539 cbreak();
540 noecho();
541
542 aio->use_colour = true;
543
544 if (!has_colors())
545 aio->use_colour = false;
546
547 if (no_color != NULL && no_color[0] != '\0')
548 aio->use_colour = false;
549
550 if (aio->use_colour) {
551 start_color();
552 use_default_colors();
553 init_pair(COLOR_CONTROL_SELECTED, COLOR_BLUE, COLOR_BLACK);
554 init_pair(COLOR_LEVELS, COLOR_GREEN, COLOR_BLACK);
555 init_pair(COLOR_SET_SELECTED, COLOR_BLACK, COLOR_GREEN);
556 init_pair(COLOR_ENUM_ON, COLOR_WHITE, COLOR_RED);
557 init_pair(COLOR_ENUM_OFF, COLOR_WHITE, COLOR_BLUE);
558 init_pair(COLOR_ENUM_MISC, COLOR_BLACK, COLOR_YELLOW);
559 }
560
561 if (mixer_device != NULL) {
562 open_device(aio, mixer_device);
563 } else {
564 for (i = 0; i < 16; ++i) {
565 (void)snprintf(mixer_path, sizeof(mixer_path),
566 "/dev/mixer%d", i);
567 fd = open(mixer_path, O_RDWR);
568 if (fd == -1)
569 break;
570 close(fd);
571 mixer_count++;
572 }
573
574 if (mixer_count > 1) {
575 process_device_select(aio, mixer_count);
576 } else {
577 open_device(aio, _PATH_MIXER);
578 }
579 }
580
581 endwin();
582 close(aio->fd);
583 free(aio);
584
585 return 0;
586 }
587