1 /* $NetBSD: main.c,v 1.5 2023/06/29 19:06:54 nia 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 delta = right ? info->un.v.delta : -info->un.v.delta; 153 /* 154 * work around strange problem where the level can be 155 * increased but not decreased, seen with uaudio(4) 156 */ 157 if (delta < 16) 158 delta *= 2; 159 if (aio->channels_unlocked) { 160 level = &value.un.value.level[control->setindex]; 161 *level = (unsigned char)adjust_level(*level, delta); 162 } else { 163 for (i = 0; i < value.un.value.num_channels; ++i) { 164 level = &value.un.value.level[i]; 165 *level = (unsigned char)adjust_level(*level, delta); 166 } 167 } 168 break; 169 case AUDIO_MIXER_ENUM: 170 for (i = 0; i < info->un.e.num_mem; ++i) { 171 if (info->un.e.member[i].ord == value.un.ord) { 172 cur_index = i; 173 break; 174 } 175 } 176 if (right) { 177 value.un.ord = cur_index < (info->un.e.num_mem - 1) ? 178 info->un.e.member[cur_index + 1].ord : 179 info->un.e.member[0].ord; 180 } else { 181 value.un.ord = cur_index > 0 ? 182 info->un.e.member[cur_index - 1].ord : 183 info->un.e.member[control->info.un.e.num_mem - 1].ord; 184 } 185 break; 186 case AUDIO_MIXER_SET: 187 if (right) { 188 control->setindex = 189 control->setindex < (info->un.s.num_mem - 1) ? 190 control->setindex + 1 : 0; 191 } else { 192 control->setindex = control->setindex > 0 ? 193 control->setindex - 1 : 194 control->info.un.s.num_mem - 1; 195 } 196 break; 197 } 198 199 if (info->type != AUDIO_MIXER_SET) { 200 if (ioctl(aio->fd, AUDIO_MIXER_WRITE, &value) < 0) 201 err(EXIT_FAILURE, "failed to adjust mixer control"); 202 } 203 204 draw_control(aio, control, true); 205 } 206 207 static int 208 toggle_set(struct aiomixer *aio) 209 { 210 struct mixer_ctrl ctrl; 211 struct aiomixer_class *class = &aio->classes[aio->curclass]; 212 struct aiomixer_control *control = &class->controls[aio->curcontrol]; 213 214 ctrl.dev = control->info.index; 215 ctrl.type = control->info.type; 216 217 if (control->info.type != AUDIO_MIXER_SET) 218 return -1; 219 220 if (ioctl(aio->fd, AUDIO_MIXER_READ, &ctrl) < 0) 221 err(EXIT_FAILURE, "failed to read mixer control"); 222 223 ctrl.un.mask ^= control->info.un.s.member[control->setindex].mask; 224 225 if (ioctl(aio->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) 226 err(EXIT_FAILURE, "failed to read mixer control"); 227 228 draw_control(aio, control, true); 229 return 0; 230 } 231 232 static void 233 step_up(struct aiomixer *aio) 234 { 235 struct aiomixer_class *class; 236 struct aiomixer_control *control; 237 238 class = &aio->classes[aio->curclass]; 239 control = &class->controls[aio->curcontrol]; 240 241 if (aio->channels_unlocked && 242 control->info.type == AUDIO_MIXER_VALUE && 243 control->setindex > 0) { 244 control->setindex--; 245 draw_control(aio, control, true); 246 return; 247 } 248 select_control(aio, aio->curcontrol - 1); 249 } 250 251 static void 252 step_down(struct aiomixer *aio) 253 { 254 struct aiomixer_class *class; 255 struct aiomixer_control *control; 256 257 class = &aio->classes[aio->curclass]; 258 control = &class->controls[aio->curcontrol]; 259 260 if (aio->channels_unlocked && 261 control->info.type == AUDIO_MIXER_VALUE && 262 control->setindex < (control->info.un.v.num_channels - 1)) { 263 control->setindex++; 264 draw_control(aio, control, true); 265 return; 266 } 267 268 select_control(aio, (aio->curcontrol + 1) % class->numcontrols); 269 } 270 271 static int 272 read_key(struct aiomixer *aio, int ch) 273 { 274 struct aiomixer_class *class; 275 struct aiomixer_control *control; 276 size_t i; 277 278 switch (ch) { 279 case KEY_RESIZE: 280 class = &aio->classes[aio->curclass]; 281 resize_widgets(aio); 282 draw_header(aio); 283 draw_classbar(aio); 284 for (i = 0; i < class->numcontrols; ++i) { 285 draw_control(aio, 286 &class->controls[i], 287 aio->state == STATE_CONTROL_SELECT ? 288 (aio->curcontrol == i) : false); 289 } 290 break; 291 case KEY_LEFT: 292 case 'h': 293 if (aio->state == STATE_CLASS_SELECT) { 294 select_class(aio, aio->curclass > 0 ? 295 aio->curclass - 1 : aio->numclasses - 1); 296 } else if (aio->state == STATE_CONTROL_SELECT) { 297 class = &aio->classes[aio->curclass]; 298 slide_control(aio, 299 &class->controls[aio->curcontrol], false); 300 } 301 break; 302 case KEY_RIGHT: 303 case 'l': 304 if (aio->state == STATE_CLASS_SELECT) { 305 select_class(aio, 306 (aio->curclass + 1) % aio->numclasses); 307 } else if (aio->state == STATE_CONTROL_SELECT) { 308 class = &aio->classes[aio->curclass]; 309 slide_control(aio, 310 &class->controls[aio->curcontrol], true); 311 } 312 break; 313 case KEY_UP: 314 case 'k': 315 if (aio->state == STATE_CONTROL_SELECT) { 316 if (aio->curcontrol == 0) { 317 class = &aio->classes[aio->curclass]; 318 control = &class->controls[aio->curcontrol]; 319 control->setindex = -1; 320 aio->state = STATE_CLASS_SELECT; 321 draw_control(aio, control, false); 322 } else { 323 step_up(aio); 324 } 325 } 326 break; 327 case KEY_DOWN: 328 case 'j': 329 if (aio->state == STATE_CLASS_SELECT) { 330 class = &aio->classes[aio->curclass]; 331 if (class->numcontrols > 0) { 332 aio->state = STATE_CONTROL_SELECT; 333 select_control(aio, 0); 334 } 335 } else if (aio->state == STATE_CONTROL_SELECT) { 336 step_down(aio); 337 } 338 break; 339 case '\n': 340 case ' ': 341 if (aio->state == STATE_CONTROL_SELECT) 342 toggle_set(aio); 343 break; 344 case '1': 345 select_class(aio, 0); 346 break; 347 case '2': 348 select_class(aio, 1); 349 break; 350 case '3': 351 select_class(aio, 2); 352 break; 353 case '4': 354 select_class(aio, 3); 355 break; 356 case '5': 357 select_class(aio, 4); 358 break; 359 case '6': 360 select_class(aio, 5); 361 break; 362 case '7': 363 select_class(aio, 6); 364 break; 365 case '8': 366 select_class(aio, 7); 367 break; 368 case '9': 369 select_class(aio, 8); 370 break; 371 case 'q': 372 case '\e': 373 if (aio->state == STATE_CONTROL_SELECT) { 374 class = &aio->classes[aio->curclass]; 375 control = &class->controls[aio->curcontrol]; 376 aio->state = STATE_CLASS_SELECT; 377 draw_control(aio, control, false); 378 break; 379 } 380 return 1; 381 case 'u': 382 aio->channels_unlocked = !aio->channels_unlocked; 383 if (aio->state == STATE_CONTROL_SELECT) { 384 class = &aio->classes[aio->curclass]; 385 control = &class->controls[aio->curcontrol]; 386 if (control->info.type == AUDIO_MIXER_VALUE) 387 draw_control(aio, control, true); 388 } 389 break; 390 } 391 392 draw_screen(aio); 393 return 0; 394 } 395 396 static void 397 process_device_select(struct aiomixer *aio, unsigned int num_devices) 398 { 399 unsigned int selected_device = 0; 400 char device_path[16]; 401 int ch; 402 403 draw_mixer_select(num_devices, selected_device); 404 405 while ((ch = getch()) != ERR) { 406 switch (ch) { 407 case '\n': 408 clear(); 409 (void)snprintf(device_path, sizeof(device_path), 410 "/dev/mixer%d", selected_device); 411 open_device(aio, device_path); 412 return; 413 case KEY_UP: 414 case 'k': 415 if (selected_device > 0) 416 selected_device--; 417 else 418 selected_device = (num_devices - 1); 419 break; 420 case KEY_DOWN: 421 case 'j': 422 if (selected_device < (num_devices - 1)) 423 selected_device++; 424 else 425 selected_device = 0; 426 break; 427 case '1': 428 selected_device = 0; 429 break; 430 case '2': 431 selected_device = 1; 432 break; 433 case '3': 434 selected_device = 2; 435 break; 436 case '4': 437 selected_device = 3; 438 break; 439 case '5': 440 selected_device = 4; 441 break; 442 case '6': 443 selected_device = 5; 444 break; 445 case '7': 446 selected_device = 6; 447 break; 448 case '8': 449 selected_device = 7; 450 break; 451 case '9': 452 selected_device = 8; 453 break; 454 } 455 draw_mixer_select(num_devices, selected_device); 456 } 457 } 458 459 static void 460 open_device(struct aiomixer *aio, const char *device) 461 { 462 int ch; 463 464 if ((aio->fd = open(device, O_RDWR)) < 0) 465 err(EXIT_FAILURE, "couldn't open mixer device"); 466 467 if (ioctl(aio->fd, AUDIO_GETDEV, &aio->mixerdev) < 0) 468 err(EXIT_FAILURE, "AUDIO_GETDEV failed"); 469 470 aio->state = STATE_CLASS_SELECT; 471 472 aiomixer_parse(aio); 473 474 create_widgets(aio); 475 476 draw_header(aio); 477 select_class(aio, 0); 478 draw_screen(aio); 479 480 while ((ch = getch()) != ERR) { 481 if (read_key(aio, ch) != 0) 482 break; 483 } 484 } 485 486 static __dead void 487 on_signal(int dummy) 488 { 489 endwin(); 490 exit(0); 491 } 492 493 int 494 main(int argc, char **argv) 495 { 496 const char *mixer_device = NULL; 497 extern char *optarg; 498 extern int optind; 499 struct aiomixer *aio; 500 char mixer_path[32]; 501 unsigned int mixer_count = 0; 502 int i, fd; 503 int ch; 504 char *no_color = getenv("NO_COLOR"); 505 506 if ((aio = malloc(sizeof(struct aiomixer))) == NULL) { 507 err(EXIT_FAILURE, "malloc failed"); 508 } 509 510 while ((ch = getopt(argc, argv, "d:u")) != -1) { 511 switch (ch) { 512 case 'd': 513 mixer_device = optarg; 514 break; 515 case 'u': 516 aio->channels_unlocked = true; 517 break; 518 default: 519 usage(); 520 break; 521 } 522 } 523 524 argc -= optind; 525 argv += optind; 526 527 if (initscr() == NULL) 528 err(EXIT_FAILURE, "can't initialize curses"); 529 530 (void)signal(SIGHUP, on_signal); 531 (void)signal(SIGINT, on_signal); 532 (void)signal(SIGTERM, on_signal); 533 534 curs_set(0); 535 keypad(stdscr, TRUE); 536 cbreak(); 537 noecho(); 538 539 aio->use_colour = true; 540 541 if (!has_colors()) 542 aio->use_colour = false; 543 544 if (no_color != NULL && no_color[0] != '\0') 545 aio->use_colour = false; 546 547 if (aio->use_colour) { 548 start_color(); 549 use_default_colors(); 550 init_pair(COLOR_CONTROL_SELECTED, COLOR_BLUE, COLOR_BLACK); 551 init_pair(COLOR_LEVELS, COLOR_GREEN, COLOR_BLACK); 552 init_pair(COLOR_SET_SELECTED, COLOR_BLACK, COLOR_GREEN); 553 init_pair(COLOR_ENUM_ON, COLOR_WHITE, COLOR_RED); 554 init_pair(COLOR_ENUM_OFF, COLOR_WHITE, COLOR_BLUE); 555 init_pair(COLOR_ENUM_MISC, COLOR_BLACK, COLOR_YELLOW); 556 } 557 558 if (mixer_device != NULL) { 559 open_device(aio, mixer_device); 560 } else { 561 for (i = 0; i < 16; ++i) { 562 (void)snprintf(mixer_path, sizeof(mixer_path), 563 "/dev/mixer%d", i); 564 fd = open(mixer_path, O_RDWR); 565 if (fd == -1) 566 break; 567 close(fd); 568 mixer_count++; 569 } 570 571 if (mixer_count > 1) { 572 process_device_select(aio, mixer_count); 573 } else { 574 open_device(aio, _PATH_MIXER); 575 } 576 } 577 578 endwin(); 579 close(aio->fd); 580 free(aio); 581 582 return 0; 583 } 584