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 
43 snd_mixer_t *mixer;
44 char *mixer_device_name;
45 bool unplugged;
46 
47 struct widget mixer_widget;
48 
49 enum view_mode view_mode;
50 
51 int focus_control_index;
52 snd_mixer_selem_id_t *current_selem_id;
53 unsigned int current_control_flags;
54 
55 bool control_values_changed;
56 bool controls_changed;
57 
58 unsigned int mouse_wheel_step = 1;
59 bool mouse_wheel_focuses_control = 1;
60 
elem_callback(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED, unsigned int mask)61 static 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 
mixer_callback(snd_mixer_t *mixer ATTRIBUTE_UNUSED, unsigned int mask, snd_mixer_elem_t *elem)76 static 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 
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 >= 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 
get_focus_control(unsigned int type)240 static 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 
change_enum_to_percent(struct control *control, int value)251 static 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 
change_enum_relative(struct control *control, int delta)279 static 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 
change_volume_to_percent(struct control *control, int value, unsigned int channels)305 static 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 
clamp_volume(double v)321 static 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 
change_volume_relative(struct control *control, int delta, unsigned int channels)330 static 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 
change_control_to_percent(int value, unsigned int channels)361 static 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 
change_control_relative(int delta, unsigned int channels)375 static 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 
toggle_switches(unsigned int type, unsigned int channels)389 static 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 
toggle_mute(unsigned int channels)434 static void toggle_mute(unsigned int channels)
435 {
436 	toggle_switches(TYPE_PSWITCH, channels);
437 }
438 
toggle_capture(unsigned int channels)439 static void toggle_capture(unsigned int channels)
440 {
441 	toggle_switches(TYPE_CSWITCH, channels);
442 }
443 
balance_volumes(void)444 static 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 
on_mouse_keynull470 static 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 
on_handle_key(int key)552 static 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 
create(void)631 static 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 
on_window_size_changed(void)646 static void on_window_size_changed(void)
647 {
648 	create();
649 }
650 
on_close(void)651 static void on_close(void)
652 {
653 	widget_free(&mixer_widget);
654 }
655 
mixer_shutdown(void)656 void 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 
665 struct widget mixer_widget = {
666 	.handle_key = on_handle_key,
667 	.window_size_changed = on_window_size_changed,
668 	.close = on_close,
669 };
670 
create_mixer_widget(void)671 void create_mixer_widget(void)
672 {
673 	create();
674 }
675