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 
elem_callback(snd_mixer_elem_t * elem,unsigned int mask)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 
mixer_callback(snd_mixer_t * mixer,unsigned int mask,snd_mixer_elem_t * elem)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 
create_mixer_object(struct snd_mixer_selem_regopt * selem_regopt)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 
set_view_mode(enum view_mode m)109 static void set_view_mode(enum view_mode m)
110 {
111 	view_mode = m;
112 	create_controls();
113 }
114 
close_hctl(void)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 
check_unplugged(void)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 
close_mixer_device(void)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 
select_card_by_name(const char * device_name)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 
show_help(void)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 
refocus_control(void)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 
get_focus_control(unsigned int type)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 
change_enum_to_percent(struct control * control,int value)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 
change_enum_relative(struct control * control,int delta)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 
change_volume_to_percent(struct control * control,int value,unsigned int channels)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 
clamp_volume(double v)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 
change_volume_relative(struct control * control,int delta,unsigned int channels)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 
change_control_to_percent(int value,unsigned int channels)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 
change_control_relative(int delta,unsigned int channels)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 
toggle_switches(unsigned int type,unsigned int channels)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 
toggle_mute(unsigned int channels)433 static void toggle_mute(unsigned int channels)
434 {
435 	toggle_switches(TYPE_PSWITCH, channels);
436 }
437 
toggle_capture(unsigned int channels)438 static void toggle_capture(unsigned int channels)
439 {
440 	toggle_switches(TYPE_CSWITCH, channels);
441 }
442 
balance_volumes(void)443 static void balance_volumes(void)
444 {
445 	struct control *control;
446 	double left, right;
447 
448 	control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME);
449 	if (!control || !(control->flags & HAS_VOLUME_1))
450 		return;
451 	if (control->flags & TYPE_PVOLUME) {
452 		left = get_normalized_playback_volume(control->elem, control->volume_channels[0]);
453 		right = get_normalized_playback_volume(control->elem, control->volume_channels[1]);
454 	} else {
455 		left = get_normalized_capture_volume(control->elem, control->volume_channels[0]);
456 		right = get_normalized_capture_volume(control->elem, control->volume_channels[1]);
457 	}
458 	left = (left + right) / 2;
459 	if (control->flags & TYPE_PVOLUME) {
460 		set_normalized_playback_volume(control->elem, control->volume_channels[0], left, 0);
461 		set_normalized_playback_volume(control->elem, control->volume_channels[1], left, 0);
462 	} else {
463 		set_normalized_capture_volume(control->elem, control->volume_channels[0], left, 0);
464 		set_normalized_capture_volume(control->elem, control->volume_channels[1], left, 0);
465 	}
466 	display_controls();
467 }
468 
on_handle_key(int key)469 static void on_handle_key(int key)
470 {
471 	switch (key) {
472 	case 27:
473 	case KEY_CANCEL:
474 	case KEY_F(10):
475 		mixer_widget.close();
476 		break;
477 	case KEY_F(1):
478 	case KEY_HELP:
479 	case 'H':
480 	case 'h':
481 	case '?':
482 		show_help();
483 		break;
484 	case KEY_F(2):
485 	case '/':
486 		create_proc_files_list();
487 		break;
488 	case KEY_F(3):
489 		set_view_mode(VIEW_MODE_PLAYBACK);
490 		break;
491 	case KEY_F(4):
492 		set_view_mode(VIEW_MODE_CAPTURE);
493 		break;
494 	case KEY_F(5):
495 		set_view_mode(VIEW_MODE_ALL);
496 		break;
497 	case '\t':
498 		set_view_mode((enum view_mode)((view_mode + 1) % VIEW_MODE_COUNT));
499 		break;
500 	case KEY_F(6):
501 	case 'S':
502 	case 's':
503 		create_card_select_list();
504 		break;
505 	case KEY_REFRESH:
506 	case 12:
507 	case 'L':
508 	case 'l':
509 		clearok(mixer_widget.window, TRUE);
510 		display_controls();
511 		break;
512 	case KEY_LEFT:
513 	case 'P':
514 	case 'p':
515 		if (focus_control_index > 0) {
516 			--focus_control_index;
517 			refocus_control();
518 		}
519 		break;
520 	case KEY_RIGHT:
521 	case 'N':
522 	case 'n':
523 		if (focus_control_index < controls_count - 1) {
524 			++focus_control_index;
525 			refocus_control();
526 		}
527 		break;
528 	case KEY_PPAGE:
529 		change_control_relative(5, LEFT | RIGHT);
530 		break;
531 	case KEY_NPAGE:
532 		change_control_relative(-5, LEFT | RIGHT);
533 		break;
534 #if 0
535 	case KEY_BEG:
536 	case KEY_HOME:
537 		change_control_to_percent(100, LEFT | RIGHT);
538 		break;
539 #endif
540 	case KEY_LL:
541 	case KEY_END:
542 		change_control_to_percent(0, LEFT | RIGHT);
543 		break;
544 	case KEY_UP:
545 	case '+':
546 	case 'K':
547 	case 'k':
548 	case 'W':
549 	case 'w':
550 		change_control_relative(1, LEFT | RIGHT);
551 		break;
552 	case KEY_DOWN:
553 	case '-':
554 	case 'J':
555 	case 'j':
556 	case 'X':
557 	case 'x':
558 		change_control_relative(-1, LEFT | RIGHT);
559 		break;
560 	case '0': case '1': case '2': case '3': case '4':
561 	case '5': case '6': case '7': case '8': case '9':
562 		change_control_to_percent((key - '0') * 10, LEFT | RIGHT);
563 		break;
564 	case 'Q':
565 	case 'q':
566 		change_control_relative(1, LEFT);
567 		break;
568 	case 'Y':
569 	case 'y':
570 	case 'Z':
571 	case 'z':
572 		change_control_relative(-1, LEFT);
573 		break;
574 	case 'E':
575 	case 'e':
576 		change_control_relative(1, RIGHT);
577 		break;
578 	case 'C':
579 	case 'c':
580 		change_control_relative(-1, RIGHT);
581 		break;
582 	case 'M':
583 	case 'm':
584 		toggle_mute(LEFT | RIGHT);
585 		break;
586 	case 'B':
587 	case 'b':
588 	case '=':
589 		balance_volumes();
590 		break;
591 	case '<':
592 	case ',':
593 		toggle_mute(LEFT);
594 		break;
595 	case '>':
596 	case '.':
597 		toggle_mute(RIGHT);
598 		break;
599 	case ' ':
600 		toggle_capture(LEFT | RIGHT);
601 		break;
602 	case KEY_IC:
603 	case ';':
604 		toggle_capture(LEFT);
605 		break;
606 	case KEY_DC:
607 	case '\'':
608 		toggle_capture(RIGHT);
609 		break;
610 	}
611 }
612 
create(void)613 static void create(void)
614 {
615 	static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " ";
616 
617 	widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0,
618 		    attr_mixer_frame, WIDGET_BORDER);
619 	if (screen_cols >= (sizeof(title) - 1) + 2) {
620 		wattrset(mixer_widget.window, attr_mixer_active);
621 		mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title);
622 	}
623 	init_mixer_layout();
624 	display_card_info();
625 	set_view_mode(view_mode);
626 }
627 
on_window_size_changed(void)628 static void on_window_size_changed(void)
629 {
630 	create();
631 }
632 
on_close(void)633 static void on_close(void)
634 {
635 	widget_free(&mixer_widget);
636 }
637 
mixer_shutdown(void)638 void mixer_shutdown(void)
639 {
640 	free_controls();
641 	if (mixer)
642 		snd_mixer_close(mixer);
643 	if (current_selem_id)
644 		snd_mixer_selem_id_free(current_selem_id);
645 }
646 
647 struct widget mixer_widget = {
648 	.handle_key = on_handle_key,
649 	.window_size_changed = on_window_size_changed,
650 	.close = on_close,
651 };
652 
create_mixer_widget(void)653 void create_mixer_widget(void)
654 {
655 	create();
656 }
657