1/*
2 * mixer_controls.c - handles mixer controls and mapping from selems
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 <assert.h>
25#include CURSESINC
26#include <alsa/asoundlib.h>
27#include "utils.h"
28#include "mem.h"
29#include "mixer_display.h"
30#include "mixer_widget.h"
31#include "mixer_controls.h"
32
33struct control *controls;
34unsigned int controls_count;
35
36static const snd_mixer_selem_channel_id_t supported_channels[] = {
37	SND_MIXER_SCHN_FRONT_LEFT,
38	SND_MIXER_SCHN_FRONT_RIGHT,
39	SND_MIXER_SCHN_REAR_LEFT,
40	SND_MIXER_SCHN_REAR_RIGHT,
41	SND_MIXER_SCHN_FRONT_CENTER,
42	SND_MIXER_SCHN_WOOFER,
43	SND_MIXER_SCHN_SIDE_LEFT,
44	SND_MIXER_SCHN_SIDE_RIGHT,
45};
46#define LAST_SUPPORTED_CHANNEL SND_MIXER_SCHN_SIDE_RIGHT
47
48static const snd_mixer_selem_channel_id_t control_channels[][2] = {
49	{ SND_MIXER_SCHN_FRONT_LEFT, SND_MIXER_SCHN_FRONT_RIGHT },
50	{ SND_MIXER_SCHN_REAR_LEFT, SND_MIXER_SCHN_REAR_RIGHT },
51	{ SND_MIXER_SCHN_FRONT_CENTER, SND_MIXER_SCHN_UNKNOWN },
52	{ SND_MIXER_SCHN_WOOFER, SND_MIXER_SCHN_UNKNOWN },
53	{ SND_MIXER_SCHN_SIDE_LEFT, SND_MIXER_SCHN_SIDE_RIGHT },
54};
55
56bool are_there_any_controls(void)
57{
58	snd_mixer_elem_t *elem;
59	unsigned int i;
60
61	for (elem = snd_mixer_first_elem(mixer);
62	     elem;
63	     elem = snd_mixer_elem_next(elem)) {
64		if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
65			continue;
66		if (snd_mixer_selem_is_enumerated(elem))
67			return TRUE;
68		if (snd_mixer_selem_has_playback_volume_joined(elem) ||
69		    snd_mixer_selem_has_capture_volume_joined(elem) ||
70		    snd_mixer_selem_has_playback_switch_joined(elem) ||
71		    snd_mixer_selem_has_capture_switch_joined(elem))
72			return TRUE;
73		for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
74			if (snd_mixer_selem_has_playback_channel(elem, supported_channels[i]) ||
75			    snd_mixer_selem_has_capture_channel(elem, supported_channels[i]))
76				return TRUE;
77	}
78	return FALSE;
79}
80
81static bool has_more_than_front_capture_channels(snd_mixer_elem_t *elem)
82{
83	unsigned int i;
84
85	for (i = 2; i < ARRAY_SIZE(supported_channels); ++i)
86		if (snd_mixer_selem_has_capture_channel(elem, supported_channels[i]))
87			return TRUE;
88	return FALSE;
89}
90
91static bool has_any_control_channel(snd_mixer_elem_t *elem,
92				    const snd_mixer_selem_channel_id_t channels[2],
93				    int (*has_channel)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t))
94{
95	return has_channel(elem, channels[0]) ||
96	       (channels[1] != SND_MIXER_SCHN_UNKNOWN && has_channel(elem, channels[1]));
97}
98
99static bool has_merged_cswitch(snd_mixer_elem_t *elem)
100{
101	bool pvol, psw;
102	unsigned int i;
103
104	pvol = snd_mixer_selem_has_playback_volume(elem);
105	psw = snd_mixer_selem_has_playback_switch(elem);
106	if ((pvol || psw) &&
107	    snd_mixer_selem_has_capture_switch(elem) &&
108	    !snd_mixer_selem_has_capture_volume(elem)) {
109		if (snd_mixer_selem_has_capture_switch_joined(elem))
110			return TRUE;
111		else if (((pvol && snd_mixer_selem_has_playback_volume_joined(elem)) ||
112			  (psw && snd_mixer_selem_has_playback_switch_joined(elem))) &&
113			 has_more_than_front_capture_channels(elem))
114			return FALSE;
115		for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
116			if (has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_capture_channel) &&
117			    !has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_playback_channel))
118				return FALSE;
119		}
120		return TRUE;
121	}
122	return FALSE;
123}
124
125static unsigned int get_playback_controls_count(snd_mixer_elem_t *elem)
126{
127	unsigned int count = 0;
128	unsigned int i;
129	int has_vol, has_sw;
130
131	has_vol = snd_mixer_selem_has_playback_volume(elem);
132	has_sw = snd_mixer_selem_has_playback_switch(elem);
133	if (!has_vol && !has_sw)
134		return 0;
135	if ((!has_vol || snd_mixer_selem_has_playback_volume_joined(elem)) &&
136	    (!has_sw || snd_mixer_selem_has_playback_switch_joined(elem)))
137		return 1;
138	for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
139		if (snd_mixer_selem_has_playback_channel(elem, control_channels[i][0]) ||
140		    (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
141		     snd_mixer_selem_has_playback_channel(elem, control_channels[i][1])))
142			++count;
143	}
144	return count;
145}
146
147static unsigned int get_capture_controls_count(snd_mixer_elem_t *elem)
148{
149	unsigned int count = 0;
150	unsigned int i;
151	int has_vol, has_sw;
152
153	has_vol = snd_mixer_selem_has_capture_volume(elem);
154	has_sw = snd_mixer_selem_has_capture_switch(elem);
155	if ((!has_vol && !has_sw) ||
156	    (view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem)))
157		return 0;
158	if ((!has_vol || snd_mixer_selem_has_capture_volume_joined(elem)) &&
159	    (!has_sw || snd_mixer_selem_has_capture_switch_joined(elem)))
160		return 1;
161	for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
162		if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0]) ||
163		    (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
164		     snd_mixer_selem_has_capture_channel(elem, control_channels[i][1])))
165			++count;
166	}
167	return count;
168}
169
170static unsigned int get_controls_count_for_elem(snd_mixer_elem_t *elem)
171{
172	unsigned int p, c;
173
174	if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
175		return 0;
176	if (snd_mixer_selem_is_enumerated(elem)) {
177		switch (view_mode) {
178		case VIEW_MODE_PLAYBACK:
179			return snd_mixer_selem_is_enum_capture(elem) ? 0 : 1;
180		case VIEW_MODE_CAPTURE:
181			return snd_mixer_selem_is_enum_capture(elem) ? 1 : 0;
182		case VIEW_MODE_ALL:
183		default:
184			return 1;
185		}
186	}
187	switch (view_mode) {
188	case VIEW_MODE_PLAYBACK:
189		return get_playback_controls_count(elem);
190	case VIEW_MODE_CAPTURE:
191		return get_capture_controls_count(elem);
192	case VIEW_MODE_ALL:
193	default:
194		p = get_playback_controls_count(elem);
195		c = get_capture_controls_count(elem);
196		return has_merged_cswitch(elem) ? p : p + c;
197	}
198}
199
200static void create_name(struct control *control)
201{
202	unsigned int index;
203	char *s;
204
205	index = snd_mixer_selem_get_index(control->elem);
206	if (index > 0)
207		control->name = casprintf("%s %u", snd_mixer_selem_get_name(control->elem), index);
208	else
209		control->name = cstrdup(snd_mixer_selem_get_name(control->elem));
210
211	while ((s = strstr(control->name, "IEC958")) != NULL)
212		memcpy(s, "S/PDIF", 6);
213}
214
215static unsigned int create_controls_for_elem(snd_mixer_elem_t *elem, struct control *control)
216{
217	unsigned int count = 0;
218	unsigned int i;
219	unsigned int enum_index;
220	struct control *front_control = NULL;
221	bool has_pvol, has_psw;
222	bool has_cvol, has_csw;
223	bool has_channel[LAST_SUPPORTED_CHANNEL + 1];
224	bool merged_cswitch;
225	bool has_ch0, has_ch1;
226
227	if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
228		return 0;
229	if (snd_mixer_selem_is_enumerated(elem)) {
230		if ((view_mode == VIEW_MODE_PLAYBACK && snd_mixer_selem_is_enum_capture(elem)) ||
231		    (view_mode == VIEW_MODE_CAPTURE && !snd_mixer_selem_is_enum_capture(elem)))
232			return 0;
233		control->elem = elem;
234		control->flags = TYPE_ENUM;
235		control->enum_channel_bits = 0;
236		for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
237			if (snd_mixer_selem_get_enum_item(control->elem, (snd_mixer_selem_channel_id_t)i, &enum_index) >= 0)
238				control->enum_channel_bits |= 1 << i;
239		if (snd_mixer_selem_is_active(control->elem))
240			control->flags |= IS_ACTIVE;
241		create_name(control);
242		return 1;
243	}
244	has_pvol = snd_mixer_selem_has_playback_volume(elem);
245	has_psw = snd_mixer_selem_has_playback_switch(elem);
246	has_cvol = snd_mixer_selem_has_capture_volume(elem);
247	has_csw = snd_mixer_selem_has_capture_switch(elem);
248	merged_cswitch = view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem);
249	if (view_mode != VIEW_MODE_CAPTURE && (has_pvol || has_psw)) {
250		if ((!has_pvol || snd_mixer_selem_has_playback_volume_joined(elem)) &&
251		    (!has_psw || snd_mixer_selem_has_playback_switch_joined(elem))) {
252			control->elem = elem;
253			if (has_pvol) {
254				control->flags |= TYPE_PVOLUME | HAS_VOLUME_0;
255				control->volume_channels[0] = 0;
256			}
257			if (has_psw) {
258				control->flags |= TYPE_PSWITCH | HAS_PSWITCH_0;
259				control->pswitch_channels[0] = 0;
260			}
261			if (merged_cswitch) {
262				control->flags |= TYPE_CSWITCH;
263				if (snd_mixer_selem_has_capture_switch_joined(elem)) {
264					control->flags |= HAS_CSWITCH_0;
265					control->cswitch_channels[0] = 0;
266				} else {
267					if (snd_mixer_selem_has_capture_channel(elem, control_channels[0][0])) {
268						control->flags |= HAS_CSWITCH_0;
269						control->cswitch_channels[0] = control_channels[0][0];
270					}
271					if (control_channels[0][1] != SND_MIXER_SCHN_UNKNOWN &&
272					    snd_mixer_selem_has_capture_channel(elem, control_channels[0][1])) {
273						control->flags |= HAS_CSWITCH_1;
274						control->cswitch_channels[1] = control_channels[0][1];
275					}
276				}
277				if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
278					control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
279					control->cswitch_channels[0] = control->cswitch_channels[1];
280				}
281			}
282			if (snd_mixer_selem_is_active(control->elem))
283				control->flags |= IS_ACTIVE;
284			create_name(control);
285			++control;
286			++count;
287		} else {
288			for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
289				has_channel[supported_channels[i]] =
290					snd_mixer_selem_has_playback_channel(elem, supported_channels[i]);
291			for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
292				has_ch0 = has_channel[control_channels[i][0]];
293				has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
294					has_channel[control_channels[i][1]];
295				if (!has_ch0 && !has_ch1)
296					continue;
297				control->elem = elem;
298				if (has_pvol) {
299					control->flags |= TYPE_PVOLUME;
300					if (snd_mixer_selem_has_playback_volume_joined(elem)) {
301						control->flags |= HAS_VOLUME_0;
302						control->volume_channels[0] = 0;
303					} else {
304						if (has_ch0) {
305							control->flags |= HAS_VOLUME_0;
306							control->volume_channels[0] = control_channels[i][0];
307						}
308						if (has_ch1) {
309							control->flags |= HAS_VOLUME_1;
310							control->volume_channels[1] = control_channels[i][1];
311						}
312					}
313				}
314				if (has_psw) {
315					control->flags |= TYPE_PSWITCH;
316					if (snd_mixer_selem_has_playback_switch_joined(elem)) {
317						control->flags |= HAS_PSWITCH_0;
318						control->pswitch_channels[0] = 0;
319					} else {
320						if (has_ch0) {
321							control->flags |= HAS_PSWITCH_0;
322							control->pswitch_channels[0] = control_channels[i][0];
323						}
324						if (has_ch1) {
325							control->flags |= HAS_PSWITCH_1;
326							control->pswitch_channels[1] = control_channels[i][1];
327						}
328					}
329				}
330				if (merged_cswitch) {
331					control->flags |= TYPE_CSWITCH;
332					if (snd_mixer_selem_has_capture_switch_joined(elem)) {
333						control->flags |= HAS_CSWITCH_0;
334						control->cswitch_channels[0] = 0;
335					} else {
336						if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0])) {
337							control->flags |= HAS_CSWITCH_0;
338							control->cswitch_channels[0] = control_channels[i][0];
339						}
340						if (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
341						    snd_mixer_selem_has_capture_channel(elem, control_channels[i][1])) {
342							control->flags |= HAS_CSWITCH_1;
343							control->cswitch_channels[1] = control_channels[i][1];
344						}
345					}
346				}
347				if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) {
348					control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1;
349					control->volume_channels[0] = control->volume_channels[1];
350				}
351				if ((control->flags & (HAS_PSWITCH_0 | HAS_PSWITCH_1)) == HAS_PSWITCH_1) {
352					control->flags ^= HAS_PSWITCH_0 | HAS_PSWITCH_1;
353					control->pswitch_channels[0] = control->pswitch_channels[1];
354				}
355				if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
356					control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
357					control->cswitch_channels[0] = control->cswitch_channels[1];
358				}
359				if (snd_mixer_selem_is_active(control->elem))
360					control->flags |= IS_ACTIVE;
361				create_name(control);
362				if (i == 0)
363					front_control = control;
364				else {
365					front_control->flags |= IS_MULTICH | 0;
366					control->flags |= IS_MULTICH | i;
367				}
368				++control;
369				++count;
370			}
371		}
372	}
373	if (view_mode != VIEW_MODE_PLAYBACK && (has_cvol || has_csw) && !merged_cswitch) {
374		if ((!has_cvol || snd_mixer_selem_has_capture_volume_joined(elem)) &&
375		    (!has_csw || snd_mixer_selem_has_capture_switch_joined(elem))) {
376			control->elem = elem;
377			if (has_cvol) {
378				control->flags |= TYPE_CVOLUME | HAS_VOLUME_0;
379				control->volume_channels[0] = 0;
380			}
381			if (has_csw) {
382				control->flags |= TYPE_CSWITCH | HAS_CSWITCH_0;
383				control->cswitch_channels[0] = 0;
384			}
385			if (snd_mixer_selem_is_active(control->elem))
386				control->flags |= IS_ACTIVE;
387			create_name(control);
388			++control;
389			++count;
390		} else {
391			for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
392				has_channel[supported_channels[i]] =
393					snd_mixer_selem_has_capture_channel(elem, supported_channels[i]);
394			for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
395				has_ch0 = has_channel[control_channels[i][0]];
396				has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
397					has_channel[control_channels[i][1]];
398				if (!has_ch0 && !has_ch1)
399					continue;
400				control->elem = elem;
401				if (has_cvol) {
402					control->flags |= TYPE_CVOLUME;
403					if (snd_mixer_selem_has_capture_volume_joined(elem)) {
404						control->flags |= HAS_VOLUME_0;
405						control->volume_channels[0] = 0;
406					} else {
407						if (has_ch0) {
408							control->flags |= HAS_VOLUME_0;
409							control->volume_channels[0] = control_channels[i][0];
410						}
411						if (has_ch1) {
412							control->flags |= HAS_VOLUME_1;
413							control->volume_channels[1] = control_channels[i][1];
414						}
415					}
416				}
417				if (has_csw) {
418					control->flags |= TYPE_CSWITCH;
419					if (snd_mixer_selem_has_capture_switch_joined(elem)) {
420						control->flags |= HAS_CSWITCH_0;
421						control->cswitch_channels[0] = 0;
422					} else {
423						if (has_ch0) {
424							control->flags |= HAS_CSWITCH_0;
425							control->cswitch_channels[0] = control_channels[i][0];
426						}
427						if (has_ch1) {
428							control->flags |= HAS_CSWITCH_1;
429							control->cswitch_channels[1] = control_channels[i][1];
430						}
431					}
432				}
433				if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) {
434					control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1;
435					control->volume_channels[0] = control->volume_channels[1];
436				}
437				if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
438					control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
439					control->cswitch_channels[0] = control->cswitch_channels[1];
440				}
441				if (snd_mixer_selem_is_active(control->elem))
442					control->flags |= IS_ACTIVE;
443				create_name(control);
444				if (i == 0)
445					front_control = control;
446				else {
447					front_control->flags |= IS_MULTICH | 0;
448					control->flags |= IS_MULTICH | i;
449				}
450				++control;
451				++count;
452			}
453		}
454	}
455	return count;
456}
457
458static void search_for_focus_control(void)
459{
460	snd_mixer_elem_t *elem;
461	unsigned int i;
462
463	elem = snd_mixer_find_selem(mixer, current_selem_id);
464	if (elem)
465		for (i = 0; i < controls_count; ++i)
466			if (controls[i].elem == elem) {
467				focus_control_index = i;
468				for (;;) {
469					++i;
470					if (i >= controls_count || controls[i].elem != elem)
471						return;
472					if (controls[i].flags == current_control_flags) {
473						focus_control_index = i;
474						return;
475					}
476				}
477			}
478	focus_control_index = 0;
479}
480
481void free_controls(void)
482{
483	unsigned int i;
484
485	for (i = 0; i < controls_count; ++i)
486		free(controls[i].name);
487	free(controls);
488	controls = NULL;
489	controls_count = 0;
490}
491
492void create_controls(void)
493{
494	snd_mixer_elem_t *elem;
495	struct control *control;
496
497	free_controls();
498
499	for (elem = snd_mixer_first_elem(mixer);
500	     elem;
501	     elem = snd_mixer_elem_next(elem))
502		controls_count += get_controls_count_for_elem(elem);
503
504	if (controls_count > 0) {
505		controls = ccalloc(controls_count, sizeof *controls);
506		control = controls;
507		for (elem = snd_mixer_first_elem(mixer);
508		     elem;
509		     elem = snd_mixer_elem_next(elem))
510			control += create_controls_for_elem(elem, control);
511		assert(control == controls + controls_count);
512	}
513
514	compute_controls_layout();
515	display_view_mode();
516
517	search_for_focus_control();
518	refocus_control();
519}
520