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_clickable.h" 38#include "mixer_controls.h" 39#include "mixer_display.h" 40#include "mixer_widget.h" 41#include "bindings.h" 42 43snd_mixer_t *mixer; 44char *mixer_device_name; 45bool unplugged; 46 47struct widget mixer_widget; 48 49enum view_mode view_mode; 50 51int focus_control_index; 52snd_mixer_selem_id_t *current_selem_id; 53unsigned int current_control_flags; 54 55bool control_values_changed; 56bool controls_changed; 57 58unsigned int mouse_wheel_step = 1; 59bool mouse_wheel_focuses_control = 1; 60 61static int elem_callback(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED, 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 76static int mixer_callback(snd_mixer_t *mixer ATTRIBUTE_UNUSED, 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 85void 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 109static void set_view_mode(enum view_mode m) 110{ 111 view_mode = m; 112 create_controls(); 113} 114 115static 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 125static 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 145void close_mixer_device(void) 146{ 147 check_unplugged(); 148 close_hctl(); 149 150 display_card_info(); 151 set_view_mode(view_mode); 152} 153 154bool 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 187static 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 229void refocus_control(void) 230{ 231 if (focus_control_index >= 0 && 232 focus_control_index < (int)controls_count) { 233 snd_mixer_selem_get_id(controls[focus_control_index].elem, current_selem_id); 234 current_control_flags = controls[focus_control_index].flags; 235 } 236 237 display_controls(); 238} 239 240static struct control *get_focus_control(unsigned int type) 241{ 242 if (focus_control_index >= 0 && 243 focus_control_index < (int)controls_count && 244 (controls[focus_control_index].flags & IS_ACTIVE) && 245 (controls[focus_control_index].flags & type)) 246 return &controls[focus_control_index]; 247 else 248 return NULL; 249} 250 251static void change_enum_to_percent(struct control *control, int value) 252{ 253 unsigned int i; 254 unsigned int index; 255 unsigned int new_index; 256 int items; 257 int err; 258 259 i = ffs(control->enum_channel_bits) - 1; 260 err = snd_mixer_selem_get_enum_item(control->elem, i, &index); 261 if (err < 0) 262 return; 263 new_index = index; 264 if (value == 0) { 265 new_index = 0; 266 } else if (value == 100) { 267 items = snd_mixer_selem_get_enum_items(control->elem); 268 if (items < 1) 269 return; 270 new_index = items - 1; 271 } 272 if (new_index == index) 273 return; 274 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i) 275 if (control->enum_channel_bits & (1 << i)) 276 snd_mixer_selem_set_enum_item(control->elem, i, new_index); 277} 278 279static void change_enum_relative(struct control *control, int delta) 280{ 281 int items; 282 unsigned int i; 283 unsigned int index; 284 int new_index; 285 int err; 286 287 items = snd_mixer_selem_get_enum_items(control->elem); 288 if (items < 1) 289 return; 290 err = snd_mixer_selem_get_enum_item(control->elem, 0, &index); 291 if (err < 0) 292 return; 293 new_index = (int)index + delta; 294 if (new_index < 0) 295 new_index = 0; 296 else if (new_index >= items) 297 new_index = items - 1; 298 if (new_index == (int)index) 299 return; 300 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i) 301 if (control->enum_channel_bits & (1 << i)) 302 snd_mixer_selem_set_enum_item(control->elem, i, new_index); 303} 304 305static void change_volume_to_percent(struct control *control, int value, unsigned int channels) 306{ 307 int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int); 308 309 if (!(control->flags & HAS_VOLUME_1)) 310 channels = LEFT; 311 if (control->flags & TYPE_PVOLUME) 312 set_func = set_normalized_playback_volume; 313 else 314 set_func = set_normalized_capture_volume; 315 if (channels & LEFT) 316 set_func(control->elem, control->volume_channels[0], value / 100.0, 0); 317 if (channels & RIGHT) 318 set_func(control->elem, control->volume_channels[1], value / 100.0, 0); 319} 320 321static double clamp_volume(double v) 322{ 323 if (v < 0) 324 return 0; 325 if (v > 1) 326 return 1; 327 return v; 328} 329 330static void change_volume_relative(struct control *control, int delta, unsigned int channels) 331{ 332 double (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t); 333 int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int); 334 double left = 0, right = 0; 335 int dir; 336 337 if (!(control->flags & HAS_VOLUME_1)) 338 channels = LEFT; 339 if (control->flags & TYPE_PVOLUME) { 340 get_func = get_normalized_playback_volume; 341 set_func = set_normalized_playback_volume; 342 } else { 343 get_func = get_normalized_capture_volume; 344 set_func = set_normalized_capture_volume; 345 } 346 if (channels & LEFT) 347 left = get_func(control->elem, control->volume_channels[0]); 348 if (channels & RIGHT) 349 right = get_func(control->elem, control->volume_channels[1]); 350 dir = delta > 0 ? 1 : -1; 351 if (channels & LEFT) { 352 left = clamp_volume(left + delta / 100.0); 353 set_func(control->elem, control->volume_channels[0], left, dir); 354 } 355 if (channels & RIGHT) { 356 right = clamp_volume(right + delta / 100.0); 357 set_func(control->elem, control->volume_channels[1], right, dir); 358 } 359} 360 361static void change_control_to_percent(int value, unsigned int channels) 362{ 363 struct control *control; 364 365 control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM); 366 if (!control) 367 return; 368 if (control->flags & TYPE_ENUM) 369 change_enum_to_percent(control, value); 370 else 371 change_volume_to_percent(control, value, channels); 372 display_controls(); 373} 374 375static void change_control_relative(int delta, unsigned int channels) 376{ 377 struct control *control; 378 379 control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM); 380 if (!control) 381 return; 382 if (control->flags & TYPE_ENUM) 383 change_enum_relative(control, delta); 384 else 385 change_volume_relative(control, delta, channels); 386 display_controls(); 387} 388 389static void toggle_switches(unsigned int type, unsigned int channels) 390{ 391 struct control *control; 392 unsigned int switch_1_mask; 393 int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *); 394 int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int); 395 snd_mixer_selem_channel_id_t channel_ids[2]; 396 int left, right; 397 int err; 398 399 control = get_focus_control(type); 400 if (!control) 401 return; 402 if (type == TYPE_PSWITCH) { 403 switch_1_mask = HAS_PSWITCH_1; 404 get_func = snd_mixer_selem_get_playback_switch; 405 set_func = snd_mixer_selem_set_playback_switch; 406 channel_ids[0] = control->pswitch_channels[0]; 407 channel_ids[1] = control->pswitch_channels[1]; 408 } else { 409 switch_1_mask = HAS_CSWITCH_1; 410 get_func = snd_mixer_selem_get_capture_switch; 411 set_func = snd_mixer_selem_set_capture_switch; 412 channel_ids[0] = control->cswitch_channels[0]; 413 channel_ids[1] = control->cswitch_channels[1]; 414 } 415 if (!(control->flags & switch_1_mask)) 416 channels = LEFT; 417 if (channels & LEFT) { 418 err = get_func(control->elem, channel_ids[0], &left); 419 if (err < 0) 420 return; 421 } 422 if (channels & RIGHT) { 423 err = get_func(control->elem, channel_ids[1], &right); 424 if (err < 0) 425 return; 426 } 427 if (channels & LEFT) 428 set_func(control->elem, channel_ids[0], !left); 429 if (channels & RIGHT) 430 set_func(control->elem, channel_ids[1], !right); 431 display_controls(); 432} 433 434static void toggle_mute(unsigned int channels) 435{ 436 toggle_switches(TYPE_PSWITCH, channels); 437} 438 439static void toggle_capture(unsigned int channels) 440{ 441 toggle_switches(TYPE_CSWITCH, channels); 442} 443 444static void balance_volumes(void) 445{ 446 struct control *control; 447 double left, right; 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 470static int on_mouse_key() { 471 MEVENT m; 472 command_enum cmd = 0; 473 unsigned int channels = LEFT | RIGHT; 474 unsigned int index; 475 struct control *control; 476 struct clickable_rect *rect; 477 478 if (getmouse(&m) == ERR) 479 return 0; 480 481 if (m.bstate & ( 482 BUTTON1_PRESSED|BUTTON1_RELEASED| 483 BUTTON2_PRESSED|BUTTON2_RELEASED| 484 BUTTON3_PRESSED|BUTTON3_RELEASED)) 485 return 0; 486 487 rect = clickable_find(m.y, m.x); 488 if (rect) 489 cmd = rect->command; 490 491#if NCURSES_MOUSE_VERSION > 1 492 if (m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED|BUTTON5_CLICKED|BUTTON5_PRESSED)) { 493 switch (cmd) { 494 case CMD_MIXER_MOUSE_CLICK_CONTROL_ENUM: 495 focus_control_index = rect->arg1; 496 return CMD_WITH_ARG(( 497 m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED) 498 ? CMD_MIXER_CONTROL_UP 499 : CMD_MIXER_CONTROL_DOWN 500 ), 1); 501 502 case CMD_MIXER_MOUSE_CLICK_VOLUME_BAR: 503 if (mouse_wheel_focuses_control) 504 focus_control_index = rect->arg1; 505 /* fall through */ 506 507 default: 508 return CMD_WITH_ARG(( 509 m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED) 510 ? CMD_MIXER_CONTROL_UP 511 : CMD_MIXER_CONTROL_DOWN 512 ), mouse_wheel_step); 513 } 514 } 515#endif 516 517 /* If the rectangle has got an argument (value != -1) it is used for 518 * setting `focus_control_index` */ 519 if (rect && rect->arg1 >= 0) 520 focus_control_index = rect->arg1; 521 522 switch (cmd) { 523 case CMD_MIXER_MOUSE_CLICK_VOLUME_BAR: 524 if (m.bstate & (BUTTON3_CLICKED|BUTTON3_DOUBLE_CLICKED|BUTTON3_TRIPLE_CLICKED)) 525 channels = m.x - rect->x1 + 1; 526 return CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT_LEFT + channels - 1, 527 (100 * (rect->y2 - m.y) / (rect->y2 - rect->y1)) // volume 528 ); 529 530 case CMD_MIXER_MOUSE_CLICK_MUTE: 531 if (m.bstate & (BUTTON3_CLICKED|BUTTON3_DOUBLE_CLICKED|BUTTON3_TRIPLE_CLICKED)) 532 channels = m.x - rect->x1 + 1; 533 return CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, channels); 534 535 case CMD_MIXER_MOUSE_CLICK_CONTROL_ENUM: 536 control = get_focus_control(TYPE_ENUM); 537 if (control && 538 (snd_mixer_selem_get_enum_item(control->elem, 0, &index) >= 0)) { 539 return (index == 0 540 ? CMD_WITH_ARG(CMD_MIXER_CONTROL_UP, 100) 541 : CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN, 1)); 542 } 543 break; 544 545 default: 546 return cmd; // non-mouse command 547 } 548 549 return 0; // failed mouse command 550} 551 552static void on_handle_key(int key) 553{ 554 int arg; 555 command_enum cmd; 556 557 if (key == KEY_MOUSE) 558 cmd = on_mouse_key(); 559 else if (key < (int)ARRAY_SIZE(mixer_bindings)) 560 cmd = mixer_bindings[key]; 561 else 562 return; 563 564 arg = CMD_GET_ARGUMENT(cmd); 565 cmd = CMD_GET_COMMAND(cmd); 566 567 switch (cmd) { 568 case CMD_MIXER_CONTROL_DOWN_LEFT: 569 case CMD_MIXER_CONTROL_DOWN_RIGHT: 570 case CMD_MIXER_CONTROL_DOWN: 571 arg = (-arg); 572 /* fall through */ 573 case CMD_MIXER_CONTROL_UP_LEFT: 574 case CMD_MIXER_CONTROL_UP_RIGHT: 575 case CMD_MIXER_CONTROL_UP: 576 change_control_relative(arg, cmd % 4); 577 break; 578 case CMD_MIXER_CONTROL_SET_PERCENT_LEFT: 579 case CMD_MIXER_CONTROL_SET_PERCENT_RIGHT: 580 case CMD_MIXER_CONTROL_SET_PERCENT: 581 change_control_to_percent(arg, cmd % 4); 582 break; 583 case CMD_MIXER_CLOSE: 584 mixer_widget.close(); 585 break; 586 case CMD_MIXER_HELP: 587 show_help(); 588 break; 589 case CMD_MIXER_SYSTEM_INFORMATION: 590 create_proc_files_list(); 591 break; 592 case CMD_MIXER_TOGGLE_VIEW_MODE: 593 arg = (view_mode + 1) % VIEW_MODE_COUNT; 594 /* fall through */ 595 case CMD_MIXER_SET_VIEW_MODE: 596 set_view_mode((enum view_mode)(arg)); 597 break; 598 case CMD_MIXER_SELECT_CARD: 599 create_card_select_list(); 600 break; 601 case CMD_MIXER_REFRESH: 602 clearok(mixer_widget.window, TRUE); 603 display_controls(); 604 break; 605 case CMD_MIXER_PREVIOUS: 606 arg = (-arg); 607 /* fall through */ 608 case CMD_MIXER_NEXT: 609 arg = focus_control_index + arg; 610 /* fall through */ 611 case CMD_MIXER_FOCUS_CONTROL: 612 focus_control_index = arg; 613 if (focus_control_index < 0) 614 focus_control_index = 0; 615 else if (focus_control_index >= (int)controls_count) 616 focus_control_index = controls_count - 1; 617 refocus_control(); 618 break; 619 case CMD_MIXER_TOGGLE_MUTE: 620 toggle_mute(arg); 621 break; 622 case CMD_MIXER_TOGGLE_CAPTURE: 623 toggle_capture(arg); 624 break; 625 case CMD_MIXER_BALANCE_CONTROL: 626 balance_volumes(); 627 break; 628 } 629} 630 631static void create(void) 632{ 633 static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " "; 634 635 widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0, 636 attrs.mixer_frame, WIDGET_BORDER); 637 if (screen_cols >= (int)(sizeof(title) - 1) + 2) { 638 wattrset(mixer_widget.window, attrs.mixer_active); 639 mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title); 640 } 641 init_mixer_layout(); 642 display_card_info(); 643 set_view_mode(view_mode); 644} 645 646static void on_window_size_changed(void) 647{ 648 create(); 649} 650 651static void on_close(void) 652{ 653 widget_free(&mixer_widget); 654} 655 656void mixer_shutdown(void) 657{ 658 free_controls(); 659 if (mixer) 660 snd_mixer_close(mixer); 661 if (current_selem_id) 662 snd_mixer_selem_id_free(current_selem_id); 663} 664 665struct widget mixer_widget = { 666 .handle_key = on_handle_key, 667 .window_size_changed = on_window_size_changed, 668 .close = on_close, 669}; 670 671void create_mixer_widget(void) 672{ 673 create(); 674} 675