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