mixer_widget.c (15705B)
1 /* 2 * mixer_widget.c - mixer widget and keys handling 3 * Copyright (c) 1998,1999 Tim Janik 4 * Jaroslav Kysela <perex@perex.cz> 5 * Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de> 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 #include "aconfig.h" 22 #include <stdlib.h> 23 #include <string.h> 24 #include <errno.h> 25 #include <alsa/asoundlib.h> 26 #include "gettext_curses.h" 27 #include "version.h" 28 #include "utils.h" 29 #include "die.h" 30 #include "mem.h" 31 #include "colors.h" 32 #include "widget.h" 33 #include "textbox.h" 34 #include "proc_files.h" 35 #include "card_select.h" 36 #include "volume_mapping.h" 37 #include "mixer_controls.h" 38 #include "mixer_display.h" 39 #include "mixer_widget.h" 40 41 snd_mixer_t *mixer; 42 char *mixer_device_name; 43 bool unplugged; 44 45 struct widget mixer_widget; 46 47 enum view_mode view_mode; 48 49 int focus_control_index; 50 snd_mixer_selem_id_t *current_selem_id; 51 unsigned int current_control_flags; 52 53 bool control_values_changed; 54 bool controls_changed; 55 56 enum channel_mask { 57 LEFT = 1, 58 RIGHT = 2, 59 }; 60 61 static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask) 62 { 63 if (mask == SND_CTL_EVENT_MASK_REMOVE) { 64 controls_changed = TRUE; 65 } else { 66 if (mask & SND_CTL_EVENT_MASK_VALUE) 67 control_values_changed = TRUE; 68 69 if (mask & SND_CTL_EVENT_MASK_INFO) 70 controls_changed = TRUE; 71 } 72 73 return 0; 74 } 75 76 static int mixer_callback(snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem) 77 { 78 if (mask & SND_CTL_EVENT_MASK_ADD) { 79 snd_mixer_elem_set_callback(elem, elem_callback); 80 controls_changed = TRUE; 81 } 82 return 0; 83 } 84 85 void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt) 86 { 87 int err; 88 89 err = snd_mixer_open(&mixer, 0); 90 if (err < 0) 91 fatal_alsa_error(_("cannot open mixer"), err); 92 93 mixer_device_name = cstrdup(selem_regopt->device); 94 err = snd_mixer_selem_register(mixer, selem_regopt, NULL); 95 if (err < 0) 96 fatal_alsa_error(_("cannot open mixer"), err); 97 98 snd_mixer_set_callback(mixer, mixer_callback); 99 100 err = snd_mixer_load(mixer); 101 if (err < 0) 102 fatal_alsa_error(_("cannot load mixer controls"), err); 103 104 err = snd_mixer_selem_id_malloc(¤t_selem_id); 105 if (err < 0) 106 fatal_error("out of memory"); 107 } 108 109 static void set_view_mode(enum view_mode m) 110 { 111 view_mode = m; 112 create_controls(); 113 } 114 115 static void close_hctl(void) 116 { 117 free_controls(); 118 if (mixer_device_name) { 119 snd_mixer_detach(mixer, mixer_device_name); 120 free(mixer_device_name); 121 mixer_device_name = NULL; 122 } 123 } 124 125 static void check_unplugged(void) 126 { 127 snd_hctl_t *hctl; 128 snd_ctl_t *ctl; 129 unsigned int state; 130 int err; 131 132 unplugged = FALSE; 133 if (mixer_device_name) { 134 err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl); 135 if (err >= 0) { 136 ctl = snd_hctl_ctl(hctl); 137 /* just any random function that does an ioctl() */ 138 err = snd_ctl_get_power_state(ctl, &state); 139 if (err == -ENODEV) 140 unplugged = TRUE; 141 } 142 } 143 } 144 145 void close_mixer_device(void) 146 { 147 check_unplugged(); 148 close_hctl(); 149 150 display_card_info(); 151 set_view_mode(view_mode); 152 } 153 154 bool select_card_by_name(const char *device_name) 155 { 156 int err; 157 bool opened; 158 char *msg; 159 160 close_hctl(); 161 unplugged = FALSE; 162 163 opened = FALSE; 164 if (device_name) { 165 err = snd_mixer_attach(mixer, device_name); 166 if (err >= 0) 167 opened = TRUE; 168 else { 169 msg = casprintf(_("Cannot open mixer device '%s'."), device_name); 170 show_alsa_error(msg, err); 171 free(msg); 172 } 173 } 174 if (opened) { 175 mixer_device_name = cstrdup(device_name); 176 177 err = snd_mixer_load(mixer); 178 if (err < 0) 179 fatal_alsa_error(_("cannot load mixer controls"), err); 180 } 181 182 display_card_info(); 183 set_view_mode(view_mode); 184 return opened; 185 } 186 187 static void show_help(void) 188 { 189 const char *help[] = { 190 _("Esc Exit"), 191 _("F1 ? H Help"), 192 _("F2 / System information"), 193 _("F3 Show playback controls"), 194 _("F4 Show capture controls"), 195 _("F5 Show all controls"), 196 _("Tab Toggle view mode (F3/F4/F5)"), 197 _("F6 S Select sound card"), 198 _("L Redraw screen"), 199 "", 200 _("Left Move to the previous control"), 201 _("Right Move to the next control"), 202 "", 203 _("Up/Down Change volume"), 204 _("+ - Change volume"), 205 _("Page Up/Dn Change volume in big steps"), 206 _("End Set volume to 0%"), 207 _("0-9 Set volume to 0%-90%"), 208 _("Q W E Increase left/both/right volumes"), 209 /* TRANSLATORS: or Y instead of Z */ 210 _("Z X C Decrease left/both/right volumes"), 211 _("B Balance left and right volumes"), 212 "", 213 _("M Toggle mute"), 214 /* TRANSLATORS: or , . */ 215 _("< > Toggle left/right mute"), 216 "", 217 _("Space Toggle capture"), 218 /* TRANSLATORS: or Insert Delete */ 219 _("; ' Toggle left/right capture"), 220 "", 221 _("Authors:"), 222 _(" Tim Janik"), 223 _(" Jaroslav Kysela <perex@perex.cz>"), 224 _(" Clemens Ladisch <clemens@ladisch.de>"), 225 }; 226 show_text(help, ARRAY_SIZE(help), _("Help")); 227 } 228 229 void refocus_control(void) 230 { 231 if (focus_control_index < controls_count) { 232 snd_mixer_selem_get_id(controls[focus_control_index].elem, current_selem_id); 233 current_control_flags = controls[focus_control_index].flags; 234 } 235 236 display_controls(); 237 } 238 239 static struct control *get_focus_control(unsigned int type) 240 { 241 if (focus_control_index >= 0 && 242 focus_control_index < controls_count && 243 (controls[focus_control_index].flags & IS_ACTIVE) && 244 (controls[focus_control_index].flags & type)) 245 return &controls[focus_control_index]; 246 else 247 return NULL; 248 } 249 250 static void change_enum_to_percent(struct control *control, int value) 251 { 252 unsigned int i; 253 unsigned int index; 254 unsigned int new_index; 255 int items; 256 int err; 257 258 i = ffs(control->enum_channel_bits) - 1; 259 err = snd_mixer_selem_get_enum_item(control->elem, i, &index); 260 if (err < 0) 261 return; 262 new_index = index; 263 if (value == 0) { 264 new_index = 0; 265 } else if (value == 100) { 266 items = snd_mixer_selem_get_enum_items(control->elem); 267 if (items < 1) 268 return; 269 new_index = items - 1; 270 } 271 if (new_index == index) 272 return; 273 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i) 274 if (control->enum_channel_bits & (1 << i)) 275 snd_mixer_selem_set_enum_item(control->elem, i, new_index); 276 } 277 278 static void change_enum_relative(struct control *control, int delta) 279 { 280 int items; 281 unsigned int i; 282 unsigned int index; 283 int new_index; 284 int err; 285 286 items = snd_mixer_selem_get_enum_items(control->elem); 287 if (items < 1) 288 return; 289 err = snd_mixer_selem_get_enum_item(control->elem, 0, &index); 290 if (err < 0) 291 return; 292 new_index = (int)index + delta; 293 if (new_index < 0) 294 new_index = 0; 295 else if (new_index >= items) 296 new_index = items - 1; 297 if (new_index == index) 298 return; 299 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i) 300 if (control->enum_channel_bits & (1 << i)) 301 snd_mixer_selem_set_enum_item(control->elem, i, new_index); 302 } 303 304 static void change_volume_to_percent(struct control *control, int value, unsigned int channels) 305 { 306 int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int); 307 308 if (!(control->flags & HAS_VOLUME_1)) 309 channels = LEFT; 310 if (control->flags & TYPE_PVOLUME) 311 set_func = set_normalized_playback_volume; 312 else 313 set_func = set_normalized_capture_volume; 314 if (channels & LEFT) 315 set_func(control->elem, control->volume_channels[0], value / 100.0, 0); 316 if (channels & RIGHT) 317 set_func(control->elem, control->volume_channels[1], value / 100.0, 0); 318 } 319 320 static double clamp_volume(double v) 321 { 322 if (v < 0) 323 return 0; 324 if (v > 1) 325 return 1; 326 return v; 327 } 328 329 static void change_volume_relative(struct control *control, int delta, unsigned int channels) 330 { 331 double (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t); 332 int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int); 333 double left, right; 334 int dir; 335 336 if (!(control->flags & HAS_VOLUME_1)) 337 channels = LEFT; 338 if (control->flags & TYPE_PVOLUME) { 339 get_func = get_normalized_playback_volume; 340 set_func = set_normalized_playback_volume; 341 } else { 342 get_func = get_normalized_capture_volume; 343 set_func = set_normalized_capture_volume; 344 } 345 if (channels & LEFT) 346 left = get_func(control->elem, control->volume_channels[0]); 347 if (channels & RIGHT) 348 right = get_func(control->elem, control->volume_channels[1]); 349 dir = delta > 0 ? 1 : -1; 350 if (channels & LEFT) { 351 left = clamp_volume(left + delta / 100.0); 352 set_func(control->elem, control->volume_channels[0], left, dir); 353 } 354 if (channels & RIGHT) { 355 right = clamp_volume(right + delta / 100.0); 356 set_func(control->elem, control->volume_channels[1], right, dir); 357 } 358 } 359 360 static void change_control_to_percent(int value, unsigned int channels) 361 { 362 struct control *control; 363 364 control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM); 365 if (!control) 366 return; 367 if (control->flags & TYPE_ENUM) 368 change_enum_to_percent(control, value); 369 else 370 change_volume_to_percent(control, value, channels); 371 display_controls(); 372 } 373 374 static void change_control_relative(int delta, unsigned int channels) 375 { 376 struct control *control; 377 378 control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM); 379 if (!control) 380 return; 381 if (control->flags & TYPE_ENUM) 382 change_enum_relative(control, delta); 383 else 384 change_volume_relative(control, delta, channels); 385 display_controls(); 386 } 387 388 static void toggle_switches(unsigned int type, unsigned int channels) 389 { 390 struct control *control; 391 unsigned int switch_1_mask; 392 int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *); 393 int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int); 394 snd_mixer_selem_channel_id_t channel_ids[2]; 395 int left, right; 396 int err; 397 398 control = get_focus_control(type); 399 if (!control) 400 return; 401 if (type == TYPE_PSWITCH) { 402 switch_1_mask = HAS_PSWITCH_1; 403 get_func = snd_mixer_selem_get_playback_switch; 404 set_func = snd_mixer_selem_set_playback_switch; 405 channel_ids[0] = control->pswitch_channels[0]; 406 channel_ids[1] = control->pswitch_channels[1]; 407 } else { 408 switch_1_mask = HAS_CSWITCH_1; 409 get_func = snd_mixer_selem_get_capture_switch; 410 set_func = snd_mixer_selem_set_capture_switch; 411 channel_ids[0] = control->cswitch_channels[0]; 412 channel_ids[1] = control->cswitch_channels[1]; 413 } 414 if (!(control->flags & switch_1_mask)) 415 channels = LEFT; 416 if (channels & LEFT) { 417 err = get_func(control->elem, channel_ids[0], &left); 418 if (err < 0) 419 return; 420 } 421 if (channels & RIGHT) { 422 err = get_func(control->elem, channel_ids[1], &right); 423 if (err < 0) 424 return; 425 } 426 if (channels & LEFT) 427 set_func(control->elem, channel_ids[0], !left); 428 if (channels & RIGHT) 429 set_func(control->elem, channel_ids[1], !right); 430 display_controls(); 431 } 432 433 static void toggle_mute(unsigned int channels) 434 { 435 toggle_switches(TYPE_PSWITCH, channels); 436 } 437 438 static void toggle_capture(unsigned int channels) 439 { 440 toggle_switches(TYPE_CSWITCH, channels); 441 } 442 443 static void balance_volumes(void) 444 { 445 struct control *control; 446 double left, right; 447 int err; 448 449 control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME); 450 if (!control || !(control->flags & HAS_VOLUME_1)) 451 return; 452 if (control->flags & TYPE_PVOLUME) { 453 left = get_normalized_playback_volume(control->elem, control->volume_channels[0]); 454 right = get_normalized_playback_volume(control->elem, control->volume_channels[1]); 455 } else { 456 left = get_normalized_capture_volume(control->elem, control->volume_channels[0]); 457 right = get_normalized_capture_volume(control->elem, control->volume_channels[1]); 458 } 459 left = (left + right) / 2; 460 if (control->flags & TYPE_PVOLUME) { 461 set_normalized_playback_volume(control->elem, control->volume_channels[0], left, 0); 462 set_normalized_playback_volume(control->elem, control->volume_channels[1], left, 0); 463 } else { 464 set_normalized_capture_volume(control->elem, control->volume_channels[0], left, 0); 465 set_normalized_capture_volume(control->elem, control->volume_channels[1], left, 0); 466 } 467 display_controls(); 468 } 469 470 static void on_handle_key(int key) 471 { 472 switch (key) { 473 case 27: 474 case KEY_CANCEL: 475 case KEY_F(10): 476 mixer_widget.close(); 477 break; 478 case KEY_F(1): 479 case KEY_HELP: 480 case 'H': 481 case 'h': 482 case '?': 483 show_help(); 484 break; 485 case KEY_F(2): 486 case '/': 487 create_proc_files_list(); 488 break; 489 case KEY_F(3): 490 set_view_mode(VIEW_MODE_PLAYBACK); 491 break; 492 case KEY_F(4): 493 set_view_mode(VIEW_MODE_CAPTURE); 494 break; 495 case KEY_F(5): 496 set_view_mode(VIEW_MODE_ALL); 497 break; 498 case '\t': 499 set_view_mode((enum view_mode)((view_mode + 1) % VIEW_MODE_COUNT)); 500 break; 501 case KEY_F(6): 502 case 'S': 503 case 's': 504 create_card_select_list(); 505 break; 506 case KEY_REFRESH: 507 case 12: 508 case 'L': 509 case 'l': 510 clearok(mixer_widget.window, TRUE); 511 display_controls(); 512 break; 513 case KEY_LEFT: 514 case 'P': 515 case 'p': 516 if (focus_control_index > 0) { 517 --focus_control_index; 518 refocus_control(); 519 } 520 break; 521 case KEY_RIGHT: 522 case 'N': 523 case 'n': 524 if (focus_control_index < controls_count - 1) { 525 ++focus_control_index; 526 refocus_control(); 527 } 528 break; 529 case KEY_PPAGE: 530 change_control_relative(5, LEFT | RIGHT); 531 break; 532 case KEY_NPAGE: 533 change_control_relative(-5, LEFT | RIGHT); 534 break; 535 #if 0 536 case KEY_BEG: 537 case KEY_HOME: 538 change_control_to_percent(100, LEFT | RIGHT); 539 break; 540 #endif 541 case KEY_LL: 542 case KEY_END: 543 change_control_to_percent(0, LEFT | RIGHT); 544 break; 545 case KEY_UP: 546 case '+': 547 case 'K': 548 case 'k': 549 case 'W': 550 case 'w': 551 change_control_relative(1, LEFT | RIGHT); 552 break; 553 case KEY_DOWN: 554 case '-': 555 case 'J': 556 case 'j': 557 case 'X': 558 case 'x': 559 change_control_relative(-1, LEFT | RIGHT); 560 break; 561 case '0': case '1': case '2': case '3': case '4': 562 case '5': case '6': case '7': case '8': case '9': 563 change_control_to_percent((key - '0') * 10, LEFT | RIGHT); 564 break; 565 case 'Q': 566 case 'q': 567 change_control_relative(1, LEFT); 568 break; 569 case 'Y': 570 case 'y': 571 case 'Z': 572 case 'z': 573 change_control_relative(-1, LEFT); 574 break; 575 case 'E': 576 case 'e': 577 change_control_relative(1, RIGHT); 578 break; 579 case 'C': 580 case 'c': 581 change_control_relative(-1, RIGHT); 582 break; 583 case 'M': 584 case 'm': 585 toggle_mute(LEFT | RIGHT); 586 break; 587 case 'B': 588 case 'b': 589 case '=': 590 balance_volumes(); 591 break; 592 case '<': 593 case ',': 594 toggle_mute(LEFT); 595 break; 596 case '>': 597 case '.': 598 toggle_mute(RIGHT); 599 break; 600 case ' ': 601 toggle_capture(LEFT | RIGHT); 602 break; 603 case KEY_IC: 604 case ';': 605 toggle_capture(LEFT); 606 break; 607 case KEY_DC: 608 case '\'': 609 toggle_capture(RIGHT); 610 break; 611 } 612 } 613 614 static void create(void) 615 { 616 static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " "; 617 618 widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0, 619 attr_mixer_frame, WIDGET_BORDER); 620 if (screen_cols >= (sizeof(title) - 1) + 2) { 621 wattrset(mixer_widget.window, attr_mixer_active); 622 mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title); 623 } 624 init_mixer_layout(); 625 display_card_info(); 626 set_view_mode(view_mode); 627 } 628 629 static void on_window_size_changed(void) 630 { 631 create(); 632 } 633 634 static void on_close(void) 635 { 636 widget_free(&mixer_widget); 637 } 638 639 void mixer_shutdown(void) 640 { 641 free_controls(); 642 if (mixer) 643 snd_mixer_close(mixer); 644 if (current_selem_id) 645 snd_mixer_selem_id_free(current_selem_id); 646 } 647 648 struct widget mixer_widget = { 649 .handle_key = on_handle_key, 650 .window_size_changed = on_window_size_changed, 651 .close = on_close, 652 }; 653 654 void create_mixer_widget(void) 655 { 656 create(); 657 }