tarina

git clone https://git.tarina.org/tarina
Log | Files | Refs | README | LICENSE

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(&current_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 }