Home | History | Annotate | Line # | Download | only in aiomixer
      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