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