1 /* $NetBSD: main.c,v 1.2 2021/05/09 15:40:27 christos 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 (void)snprintf(device_path, sizeof(device_path), 409 "/dev/mixer%d", selected_device); 410 open_device(aio, device_path); 411 return; 412 case KEY_UP: 413 case 'k': 414 if (selected_device > 0) 415 selected_device--; 416 else 417 selected_device = (num_devices - 1); 418 break; 419 case KEY_DOWN: 420 case 'j': 421 if (selected_device < (num_devices - 1)) 422 selected_device++; 423 else 424 selected_device = 0; 425 break; 426 case '1': 427 selected_device = 0; 428 break; 429 case '2': 430 selected_device = 1; 431 break; 432 case '3': 433 selected_device = 2; 434 break; 435 case '4': 436 selected_device = 3; 437 break; 438 case '5': 439 selected_device = 4; 440 break; 441 case '6': 442 selected_device = 5; 443 break; 444 case '7': 445 selected_device = 6; 446 break; 447 case '8': 448 selected_device = 7; 449 break; 450 case '9': 451 selected_device = 8; 452 break; 453 } 454 draw_mixer_select(num_devices, selected_device); 455 } 456 } 457 458 static void 459 open_device(struct aiomixer *aio, const char *device) 460 { 461 int ch; 462 463 if ((aio->fd = open(device, O_RDWR)) < 0) 464 err(EXIT_FAILURE, "couldn't open mixer device"); 465 466 if (ioctl(aio->fd, AUDIO_GETDEV, &aio->mixerdev) < 0) 467 err(EXIT_FAILURE, "AUDIO_GETDEV failed"); 468 469 aio->state = STATE_CLASS_SELECT; 470 471 aiomixer_parse(aio); 472 473 create_widgets(aio); 474 475 draw_header(aio); 476 select_class(aio, 0); 477 draw_screen(aio); 478 479 while ((ch = getch()) != ERR) { 480 if (read_key(aio, ch) != 0) 481 break; 482 } 483 } 484 485 static __dead void 486 on_signal(int dummy) 487 { 488 endwin(); 489 exit(0); 490 } 491 492 int 493 main(int argc, char **argv) 494 { 495 const char *mixer_device = NULL; 496 extern char *optarg; 497 extern int optind; 498 struct aiomixer *aio; 499 char mixer_path[32]; 500 unsigned int mixer_count = 0; 501 int i, fd; 502 int ch; 503 504 if ((aio = malloc(sizeof(struct aiomixer))) == NULL) { 505 err(EXIT_FAILURE, "malloc failed"); 506 } 507 508 while ((ch = getopt(argc, argv, "d:u")) != -1) { 509 switch (ch) { 510 case 'd': 511 mixer_device = optarg; 512 break; 513 case 'u': 514 aio->channels_unlocked = true; 515 break; 516 default: 517 usage(); 518 break; 519 } 520 } 521 522 argc -= optind; 523 argv += optind; 524 525 if (initscr() == NULL) 526 err(EXIT_FAILURE, "can't initialize curses"); 527 528 (void)signal(SIGHUP, on_signal); 529 (void)signal(SIGINT, on_signal); 530 (void)signal(SIGTERM, on_signal); 531 532 curs_set(0); 533 keypad(stdscr, TRUE); 534 cbreak(); 535 noecho(); 536 537 if (has_colors()) { 538 start_color(); 539 init_pair(COLOR_CONTROL_SELECTED, COLOR_BLUE, COLOR_BLACK); 540 init_pair(COLOR_LEVELS, COLOR_GREEN, COLOR_BLACK); 541 init_pair(COLOR_SET_SELECTED, COLOR_BLACK, COLOR_GREEN); 542 init_pair(COLOR_ENUM_ON, COLOR_WHITE, COLOR_RED); 543 init_pair(COLOR_ENUM_OFF, COLOR_WHITE, COLOR_BLUE); 544 init_pair(COLOR_ENUM_MISC, COLOR_BLACK, COLOR_YELLOW); 545 } 546 547 if (mixer_device != NULL) { 548 open_device(aio, mixer_device); 549 } else { 550 for (i = 0; i < 16; ++i) { 551 (void)snprintf(mixer_path, sizeof(mixer_path), 552 "/dev/mixer%d", i); 553 fd = open(mixer_path, O_RDWR); 554 if (fd == -1) 555 break; 556 close(fd); 557 mixer_count++; 558 } 559 560 if (mixer_count > 1) { 561 process_device_select(aio, mixer_count); 562 } else { 563 open_device(aio, _PATH_MIXER); 564 } 565 } 566 567 endwin(); 568 close(aio->fd); 569 free(aio); 570 571 return 0; 572 } 573