1/*
2 * mixer_display.c - handles displaying of mixer widget and controls
3 * Copyright (c) 1874 Lewis Carroll
4 * Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#define _C99_SOURCE /* lrint() */
21#include "aconfig.h"
22#include <stdlib.h>
23#include <string.h>
24#include <strings.h>
25#include <math.h>
26#include CURSESINC
27#include <alsa/asoundlib.h>
28#include "gettext_curses.h"
29#include "utils.h"
30#include "mem.h"
31#include "colors.h"
32#include "widget.h"
33#include "volume_mapping.h"
34#include "mixer_widget.h"
35#include "mixer_controls.h"
36#include "mixer_display.h"
37#include "mixer_clickable.h"
38
39enum align {
40	ALIGN_LEFT,
41	ALIGN_RIGHT,
42	ALIGN_CENTER,
43};
44
45static bool screen_too_small;
46static bool has_info_items;
47
48static int info_items_left;
49static int info_items_width;
50
51static int visible_controls;
52static int first_visible_control_index;
53static int first_control_x;
54static int control_width;
55static int control_name_width;
56
57static int base_y;
58static int volume_height;
59static int cswitch_y;
60static int values_y;
61static int name_y;
62static int channel_name_y;
63
64static void display_string_in_field(int y, int x, const char *s, int width, enum align align)
65{
66	int string_width;
67	const char *s_end;
68	int spaces;
69	int cur_y, cur_x;
70
71	wmove(mixer_widget.window, y, x);
72	string_width = width;
73	s_end = mbs_at_width(s, &string_width, -1);
74	if (string_width >= width) {
75		waddnstr(mixer_widget.window, s, s_end - s);
76	} else {
77		if (align != ALIGN_LEFT) {
78			spaces = width - string_width;
79			if (align == ALIGN_CENTER)
80				spaces /= 2;
81			if (spaces > 0)
82				wprintw(mixer_widget.window, "%*s", spaces, "");
83		}
84		waddstr(mixer_widget.window, s);
85		if (align != ALIGN_RIGHT) {
86			getyx(mixer_widget.window, cur_y, cur_x);
87			if (cur_y == y) {
88				spaces = x + width - cur_x;
89				if (spaces > 0)
90					wprintw(mixer_widget.window, "%*s", spaces, "");
91			}
92		}
93	}
94}
95
96void init_mixer_layout(void)
97{
98	const char *labels_left[4] = {
99		_("Card:"),
100		_("Chip:"),
101		_("View:"),
102		_("Item:"),
103	};
104	const char *labels_right[4] = {
105		_("F1:  Help"),
106		_("F2:  System information"),
107		_("F6:  Select sound card"),
108		_("Esc: Exit"),
109	};
110	int label_width_left, label_width_right;
111	int right_x, i;
112
113	clickable_clear(0, 0, -1, -1);
114	screen_too_small = screen_lines < 14 || screen_cols < 12;
115	has_info_items = screen_lines >= 6;
116	if (!has_info_items)
117		return;
118
119	label_width_left = get_max_mbs_width(labels_left, 4);
120	label_width_right = get_max_mbs_width(labels_right, 4);
121	if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols)
122		label_width_right = 0;
123	if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols)
124		label_width_left = 0;
125
126	info_items_left = label_width_left ? 3 + label_width_left : 2;
127	right_x = screen_cols - label_width_right - 2;
128	info_items_width = right_x - info_items_left;
129	if (info_items_width < 1) {
130		has_info_items = FALSE;
131		return;
132	}
133
134	wattrset(mixer_widget.window, attrs.mixer_text);
135	if (label_width_left)
136		for (i = 0; i < 4; ++i)
137			display_string_in_field(1 + i, 2, labels_left[i],
138						label_width_left, ALIGN_RIGHT);
139	if (label_width_right)
140		for (i = 0; i < 4; ++i) {
141			display_string_in_field(1 + i, right_x, labels_right[i],
142						label_width_right, ALIGN_LEFT);
143			clickable_set(1 + i, right_x, 1 + i, right_x + label_width_right - 1,
144						CMD_MIXER_HELP + i, -1);
145		}
146}
147
148void display_card_info(void)
149{
150	snd_hctl_t *hctl;
151	snd_ctl_t *ctl;
152	snd_ctl_card_info_t *card_info;
153	const char *card_name = NULL;
154	const char *mixer_name = NULL;
155	int err;
156
157	if (!has_info_items)
158		return;
159
160	snd_ctl_card_info_alloca(&card_info);
161	if (mixer_device_name)
162		err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
163	else
164		err = -1;
165	if (err >= 0) {
166		ctl = snd_hctl_ctl(hctl);
167		err = snd_ctl_card_info(ctl, card_info);
168		if (err >= 0) {
169			card_name = snd_ctl_card_info_get_name(card_info);
170			mixer_name = snd_ctl_card_info_get_mixername(card_info);
171		}
172	}
173
174	if (card_name)
175		wattrset(mixer_widget.window, attrs.mixer_active);
176	else {
177		wattrset(mixer_widget.window, attrs.mixer_text);
178		if (unplugged)
179			card_name = _("(unplugged)");
180		else
181			card_name = "-";
182	}
183	display_string_in_field(1, info_items_left, card_name, info_items_width, ALIGN_LEFT);
184
185	if (mixer_name)
186		wattrset(mixer_widget.window, attrs.mixer_active);
187	else {
188		wattrset(mixer_widget.window, attrs.mixer_text);
189		mixer_name = "-";
190	}
191	display_string_in_field(2, info_items_left, mixer_name, info_items_width, ALIGN_LEFT);
192}
193
194void display_view_mode(void)
195{
196	const char *modes[3] = {
197		_("Playback"),
198		_("Capture"),
199		_("All"),
200	};
201	int widths[3];
202	bool has_view_mode;
203	int i;
204
205	clickable_clear(3, 0, 3, 30);
206	if (!has_info_items)
207		return;
208
209	has_view_mode = controls_count > 0 || are_there_any_controls();
210	for (i = 0; i < 3; ++i)
211		widths[i] = get_mbs_width(modes[i]);
212	if (4 + widths[0] + 6 + widths[1] + 6 + widths[2] + 1 <= info_items_width) {
213		wmove(mixer_widget.window, 3, info_items_left - 1);
214		wattrset(mixer_widget.window, attrs.mixer_text);
215		for (i = 0; i < 3; ++i) {
216			wprintw(mixer_widget.window, " F%c:", '3' + i);
217			if (has_view_mode && (int)view_mode == i) {
218				wattrset(mixer_widget.window, attrs.mixer_active);
219				wprintw(mixer_widget.window, "[%s]", modes[i]);
220				wattrset(mixer_widget.window, attrs.mixer_text);
221			} else {
222				wprintw(mixer_widget.window, " %s ", modes[i]);
223			}
224			clickable_set_relative(mixer_widget.window, 0, -(widths[i] + 5), 0, -1,
225					CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, i), -1);
226		}
227	} else {
228		wattrset(mixer_widget.window, attrs.mixer_active);
229		display_string_in_field(3, info_items_left,
230					has_view_mode ? modes[view_mode] : "",
231					info_items_width, ALIGN_LEFT);
232	}
233}
234
235static char *format_gain(long db)
236{
237	if (db != SND_CTL_TLV_DB_GAIN_MUTE)
238		return casprintf("%.2f", db / 100.0);
239	else
240		return cstrdup(_("mute"));
241}
242
243static void display_focus_item_info(void)
244{
245	struct control *control;
246	unsigned int index;
247	char buf[64];
248	long db, db2;
249	int sw, sw2;
250	char *dbs, *dbs2;
251	char *value_info;
252	char *item_info;
253	int err;
254
255	if (!has_info_items)
256		return;
257	wattrset(mixer_widget.window, attrs.mixer_active);
258	if (!controls_count || screen_too_small) {
259		display_string_in_field(4, info_items_left, "", info_items_width, ALIGN_LEFT);
260		return;
261	}
262	control = &controls[focus_control_index];
263	value_info = NULL;
264	if (control->flags & TYPE_ENUM) {
265		err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index);
266		if (err >= 0)
267			err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf);
268		if (err >= 0)
269			value_info = casprintf(" [%s]", buf);
270	} else if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
271		int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
272
273		if (control->flags & TYPE_PVOLUME)
274			get_vol_func = snd_mixer_selem_get_playback_dB;
275		else
276			get_vol_func = snd_mixer_selem_get_capture_dB;
277		if (!(control->flags & HAS_VOLUME_1)) {
278			err = get_vol_func(control->elem, control->volume_channels[0], &db);
279			if (err >= 0) {
280				dbs = format_gain(db);
281				value_info = casprintf(" [%s %s]", _("dB gain:"), dbs);
282				free(dbs);
283			}
284		} else {
285			err = get_vol_func(control->elem, control->volume_channels[0], &db);
286			if (err >= 0)
287				err = get_vol_func(control->elem, control->volume_channels[1], &db2);
288			if (err >= 0) {
289				dbs = format_gain(db);
290				dbs2 = format_gain(db2);
291				value_info = casprintf(_(" [%s %s, %s]"), _("dB gain:"), dbs, dbs2);
292				free(dbs);
293				free(dbs2);
294			}
295		}
296	} else if (control->flags & TYPE_PSWITCH) {
297		if (!(control->flags & HAS_PSWITCH_1)) {
298			err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw);
299			if (err >= 0 && !sw)
300				value_info = casprintf(" [%s]", _("Off"));
301		} else {
302			err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw);
303			if (err >= 0)
304				err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &sw2);
305			if (err >= 0 && (!sw || !sw2))
306				value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off"));
307		}
308	} else if (control->flags & TYPE_CSWITCH) {
309		if (!(control->flags & HAS_CSWITCH_1)) {
310			err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw);
311			if (err >= 0 && !sw)
312				value_info = casprintf(" [%s]", _("Off"));
313		} else {
314			err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw);
315			if (err >= 0)
316				err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &sw2);
317			if (err >= 0 && (!sw || !sw2))
318				value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off"));
319		}
320	}
321	item_info = casprintf("%s%s", control->name, value_info ? value_info : "");
322	free(value_info);
323	display_string_in_field(4, info_items_left, item_info, info_items_width, ALIGN_LEFT);
324	free(item_info);
325}
326
327static void clear_controls_display(void)
328{
329	int i;
330
331	clickable_clear(5, 0, -1, -1);
332	wattrset(mixer_widget.window, attrs.mixer_frame);
333	for (i = 5; i < screen_lines - 1; ++i)
334		mvwprintw(mixer_widget.window, i, 1, "%*s", screen_cols - 2, "");
335}
336
337static void center_string(int line, const char *s)
338{
339	int width = get_mbs_width(s);
340	if (width <= screen_cols - 2)
341		mvwaddstr(mixer_widget.window, line, (screen_cols - width) / 2, s);
342}
343
344static void display_unplugged(void)
345{
346	int lines, top, left;
347	bool boojum;
348
349	lines = screen_lines - 6;
350	if (lines < 2)
351		return;
352	top = lines / 2;
353	boojum = lines >= 10 && screen_cols >= 48;
354	top -= boojum ? 5 : 1;
355	if (top < 5)
356		top = 5;
357	if (boojum) {
358		left = (screen_cols - 46) / 2;
359		wattrset(mixer_widget.window, attrs.mixer_text);
360		mvwaddstr(mixer_widget.window, top + 0, left,    "In the midst of the word he was trying to say,");
361		mvwaddstr(mixer_widget.window, top + 1, left + 2,  "In the midst of his laughter and glee,");
362		mvwaddstr(mixer_widget.window, top + 2, left,    "He had softly and suddenly vanished away---");
363		mvwaddstr(mixer_widget.window, top + 3, left + 2,  "For the Snark was a Boojum, you see.");
364		mvwchgat(mixer_widget.window,  top + 3, left + 16, 3,          /* ^^^ */
365			 attrs.mixer_text | A_BOLD, PAIR_NUMBER(attrs.mixer_text), NULL);
366		mvwaddstr(mixer_widget.window, top + 5, left,    "(Lewis Carroll, \"The Hunting of the Snark\")");
367		top += 8;
368	}
369	wattrset(mixer_widget.window, attrs.errormsg);
370	center_string(top, _("The sound device was unplugged."));
371	center_string(top + 1, _("Press F6 to select another sound card."));
372}
373
374static void display_no_controls(void)
375{
376	int y;
377	const char *msg;
378
379	y = (screen_lines - 6) / 2 - 1;
380	if (y < 5)
381		y = 5;
382	if (y >= screen_lines - 1)
383		return;
384	wattrset(mixer_widget.window, attrs.infomsg);
385	if (view_mode == VIEW_MODE_PLAYBACK && are_there_any_controls())
386		msg = _("This sound device does not have any playback controls.");
387	else if (view_mode == VIEW_MODE_CAPTURE && are_there_any_controls())
388		msg = _("This sound device does not have any capture controls.");
389	else
390		msg = _("This sound device does not have any controls.");
391	center_string(y, msg);
392}
393
394static void display_string_centered_in_control(int y, int col, const char *s, int width)
395{
396	int left, x;
397
398	left = first_control_x + col * (control_width + 1);
399	x = left + (control_width - width) / 2;
400	display_string_in_field(y, x, s, width, ALIGN_CENTER);
401}
402
403static void display_control(unsigned int control_index)
404{
405	struct control *control;
406	int col;
407	int i, c;
408	int left, frame_left;
409	int bar_height;
410	double volumes[2];
411	int switches[2];
412	unsigned int index;
413	const char *s;
414	char buf[64];
415	int err;
416
417	control = &controls[control_index];
418	col = control_index - first_visible_control_index;
419	left = first_control_x + col * (control_width + 1);
420	frame_left = left + (control_width - 4) / 2;
421	if (control->flags & IS_ACTIVE)
422		wattrset(mixer_widget.window, attrs.ctl_frame);
423	else
424		wattrset(mixer_widget.window, attrs.ctl_inactive);
425	if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
426		mvwaddch(mixer_widget.window, base_y - volume_height - 1, frame_left, ACS_ULCORNER);
427		waddch(mixer_widget.window, ACS_HLINE);
428		waddch(mixer_widget.window, ACS_HLINE);
429		waddch(mixer_widget.window, ACS_URCORNER);
430		mvwvline(mixer_widget.window, base_y - volume_height, frame_left, ACS_VLINE, volume_height);
431		mvwvline(mixer_widget.window, base_y - volume_height, frame_left + 3, ACS_VLINE, volume_height);
432		mvwaddch(mixer_widget.window, base_y, frame_left,
433			 control->flags & TYPE_PSWITCH ? ACS_LTEE : ACS_LLCORNER);
434		waddch(mixer_widget.window, ACS_HLINE);
435		waddch(mixer_widget.window, ACS_HLINE);
436		waddch(mixer_widget.window,
437		       control->flags & TYPE_PSWITCH ? ACS_RTEE : ACS_LRCORNER);
438	} else if (control->flags & TYPE_PSWITCH) {
439		mvwaddch(mixer_widget.window, base_y, frame_left, ACS_ULCORNER);
440		waddch(mixer_widget.window, ACS_HLINE);
441		waddch(mixer_widget.window, ACS_HLINE);
442		waddch(mixer_widget.window, ACS_URCORNER);
443	}
444	if (control->flags & TYPE_PSWITCH) {
445		mvwaddch(mixer_widget.window, base_y + 1, frame_left, ACS_VLINE);
446		mvwaddch(mixer_widget.window, base_y + 1, frame_left + 3, ACS_VLINE);
447		mvwaddch(mixer_widget.window, base_y + 2, frame_left, ACS_LLCORNER);
448		waddch(mixer_widget.window, ACS_HLINE);
449		waddch(mixer_widget.window, ACS_HLINE);
450		waddch(mixer_widget.window, ACS_LRCORNER);
451	}
452	if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
453		double (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t);
454
455		if (control->flags & TYPE_PVOLUME)
456			get_vol_func = get_normalized_playback_volume;
457		else
458			get_vol_func = get_normalized_capture_volume;
459		volumes[0] = get_vol_func(control->elem, control->volume_channels[0]);
460		if (control->flags & HAS_VOLUME_1)
461			volumes[1] = get_vol_func(control->elem, control->volume_channels[1]);
462		else
463			volumes[1] = volumes[0];
464
465		if (control->flags & IS_ACTIVE)
466			wattrset(mixer_widget.window, 0);
467		for (c = 0; c < 2; c++) {
468			bar_height = lrint(volumes[c] * volume_height);
469			for (i = 0; i < volume_height; ++i) {
470				chtype ch;
471				if (i + 1 > bar_height)
472					ch = ' ' | (control->flags & IS_ACTIVE ?
473						    attrs.ctl_frame : 0);
474				else {
475					ch = ACS_CKBOARD;
476					if (!(control->flags & IS_ACTIVE))
477						;
478#ifdef TRICOLOR_VOLUME_BAR
479					else if (i > volume_height * 8 / 10)
480						ch |= attrs.ctl_bar_hi;
481					else if (i > volume_height * 4 / 10)
482						ch |= attrs.ctl_bar_mi;
483#endif
484					else
485						ch |= attrs.ctl_bar_lo;
486				}
487				mvwaddch(mixer_widget.window, base_y - i - 1,
488					 frame_left + c + 1, ch);
489			}
490		}
491		clickable_set(base_y - volume_height, frame_left + 1, base_y, frame_left + 2,
492				CMD_MIXER_MOUSE_CLICK_VOLUME_BAR, control_index);
493		if (control->flags & IS_ACTIVE)
494			wattrset(mixer_widget.window, attrs.mixer_active);
495		if (!(control->flags & HAS_VOLUME_1)) {
496			sprintf(buf, "%d", (int)lrint(volumes[0] * 100));
497			display_string_in_field(values_y, frame_left - 2, buf, 8, ALIGN_CENTER);
498		} else {
499			mvwprintw(mixer_widget.window, values_y, frame_left - 2,
500				  "%3d", (int)lrint(volumes[0] * 100));
501			if (control->flags & IS_ACTIVE)
502				wattrset(mixer_widget.window, attrs.ctl_frame);
503			waddstr(mixer_widget.window, "<>");
504			if (control->flags & IS_ACTIVE)
505				wattrset(mixer_widget.window, attrs.mixer_active);
506			wprintw(mixer_widget.window, "%-3d", (int)lrint(volumes[1] * 100));
507		}
508	}
509
510	if (control->flags & TYPE_PSWITCH) {
511		err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &switches[0]);
512		if (err >= 0 && (control->flags & HAS_PSWITCH_1))
513			err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &switches[1]);
514		else
515			switches[1] = switches[0];
516		if (err < 0)
517			return;
518		if (control->flags & IS_ACTIVE)
519			wattrset(mixer_widget.window, 0);
520		mvwaddch(mixer_widget.window, base_y + 1, frame_left + 1,
521			 switches[0]
522			 /* TRANSLATORS: playback on; one character */
523			 ? _("O")[0] | (control->flags & IS_ACTIVE ? attrs.ctl_nomute : 0)
524			 /* TRANSLATORS: playback muted; one character */
525			 : _("M")[0] | (control->flags & IS_ACTIVE ? attrs.ctl_mute : 0));
526		waddch(mixer_widget.window,
527		       switches[1]
528		       ? _("O")[0] | (control->flags & IS_ACTIVE ? attrs.ctl_nomute : 0)
529		       : _("M")[0] | (control->flags & IS_ACTIVE ? attrs.ctl_mute : 0));
530		clickable_set(base_y + 1, frame_left + 1, base_y + 1, frame_left + 2,
531				CMD_MIXER_MOUSE_CLICK_MUTE, control_index);
532	}
533
534	if (control->flags & TYPE_CSWITCH) {
535		err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &switches[0]);
536		if (err >= 0 && (control->flags & HAS_CSWITCH_1))
537			err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &switches[1]);
538		else
539			switches[1] = switches[0];
540		if (err < 0)
541			return;
542		if (control->flags & IS_ACTIVE)
543			wattrset(mixer_widget.window, switches[0] ? attrs.ctl_capture : attrs.ctl_nocapture);
544		/* TRANSLATORS: "left"; no more than two characters */
545		display_string_in_field(cswitch_y - 1, frame_left - 2, switches[0] ? _("L") : "", 2, ALIGN_RIGHT);
546		clickable_set(cswitch_y - 1, frame_left - 2, cswitch_y - 1, frame_left - 1,
547				CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, LEFT), control_index);
548		if (control->flags & IS_ACTIVE)
549			wattrset(mixer_widget.window, switches[1] ? attrs.ctl_capture : attrs.ctl_nocapture);
550		/* TRANSLATORS: "right"; no more than two characters */
551		display_string_in_field(cswitch_y - 1, frame_left + 4, switches[1] ? _("R") : "", 2, ALIGN_LEFT);
552		clickable_set(cswitch_y - 1, frame_left + 4, cswitch_y - 1, frame_left + 5,
553				CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, RIGHT), control_index);
554		/* TRANSLATORS: no more than eight characters */
555		s = _("CAPTURE");
556		if (switches[0] || switches[1]) {
557			if (control->flags & IS_ACTIVE)
558				wattrset(mixer_widget.window, attrs.ctl_capture);
559			display_string_in_field(cswitch_y, frame_left - 2, s, 8, ALIGN_CENTER);
560		} else {
561			i = get_mbs_width(s);
562			if (i > 8)
563				i = 8;
564			memset(buf, '-', i);
565			buf[i] = '\0';
566			if (control->flags & IS_ACTIVE)
567				wattrset(mixer_widget.window, attrs.ctl_nocapture);
568			display_string_in_field(cswitch_y, frame_left - 2, buf, 8, ALIGN_CENTER);
569		}
570		clickable_set(cswitch_y, frame_left - 2, cswitch_y, frame_left - 2 + 8,
571				CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, LEFT|RIGHT), control_index);
572	}
573
574	if (control->flags & TYPE_ENUM) {
575		err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index);
576		if (err < 0)
577			return;
578		err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf);
579		if (err < 0)
580			return;
581		if (control->flags & IS_ACTIVE)
582			wattrset(mixer_widget.window, attrs.mixer_active);
583		display_string_centered_in_control(base_y, col, buf, control_width);
584		clickable_set_relative(mixer_widget.window, 0, -control_name_width, 0, -2,
585				CMD_MIXER_MOUSE_CLICK_CONTROL_ENUM, control_index);
586	}
587
588	if ((int)control_index == focus_control_index) {
589		i = first_control_x + col * (control_width + 1) + (control_width - control_name_width) / 2;
590		wattrset(mixer_widget.window, attrs.ctl_mark_focus);
591		mvwaddch(mixer_widget.window, name_y, i - 1, '<');
592		mvwaddch(mixer_widget.window, name_y, i + control_name_width, '>');
593		if (control->flags & IS_ACTIVE)
594			wattrset(mixer_widget.window, attrs.ctl_label_focus);
595		else
596			wattrset(mixer_widget.window, attrs.ctl_label_inactive);
597	} else {
598		if (control->flags & IS_ACTIVE)
599			wattrset(mixer_widget.window, attrs.ctl_label);
600		else
601			wattrset(mixer_widget.window, attrs.ctl_label_inactive);
602	}
603	display_string_centered_in_control(name_y, col, control->name, control_name_width);
604	clickable_set_relative(mixer_widget.window, -1, -control_name_width, 0, -2,
605			CMD_WITH_ARG(CMD_MIXER_FOCUS_CONTROL, control_index), -1);
606	if (channel_name_y > name_y) {
607		if (control->flags & IS_MULTICH) {
608			switch (control->flags & MULTICH_MASK) {
609			case 0:
610			default:
611				s = _("Front");
612				break;
613			case 1:
614				s = _("Rear");
615				break;
616			case 2:
617				s = _("Center");
618				break;
619			case 3:
620				s = _("Woofer");
621				break;
622			case 4:
623				s = _("Side");
624				break;
625			}
626		} else {
627			s = "";
628			wattrset(mixer_widget.window, attrs.mixer_frame);
629		}
630		display_string_centered_in_control(channel_name_y, col, s,
631						   control_name_width);
632	}
633}
634
635static void display_scroll_indicators(void)
636{
637	int y0, y1;
638	chtype left, right;
639
640	if (screen_too_small)
641		return;
642	y0 = screen_lines * 3 / 8;
643	y1 = screen_lines * 5 / 8;
644	left = first_visible_control_index > 0 ? ACS_LARROW : ACS_VLINE;
645	right = first_visible_control_index + visible_controls < (int)controls_count
646		? ACS_RARROW : ACS_VLINE;
647	wattrset(mixer_widget.window, attrs.mixer_frame);
648	mvwvline(mixer_widget.window, y0, 0, left, y1 - y0 + 1);
649	mvwvline(mixer_widget.window, y0, screen_cols -1, right, y1 - y0 + 1);
650	clickable_set(y0, 0, y1, 0,
651			CMD_WITH_ARG(CMD_MIXER_PREVIOUS, visible_controls), -1);
652	clickable_set(y0, screen_cols - 1, y1, screen_cols - 1,
653			CMD_WITH_ARG(CMD_MIXER_NEXT, visible_controls), -1);
654}
655
656void display_controls(void)
657{
658	int i;
659
660	if (first_visible_control_index > (int)controls_count - visible_controls)
661		first_visible_control_index = controls_count - visible_controls;
662	if (first_visible_control_index > focus_control_index)
663		first_visible_control_index = focus_control_index;
664	else if (first_visible_control_index < focus_control_index - visible_controls + 1 && visible_controls)
665		first_visible_control_index = focus_control_index - visible_controls + 1;
666
667	clear_controls_display();
668
669	display_focus_item_info();
670
671	if (controls_count > 0) {
672		if (!screen_too_small)
673			for (i = 0; i < visible_controls; ++i)
674				display_control(first_visible_control_index + i);
675	} else if (unplugged) {
676		display_unplugged();
677	} else if (mixer_device_name) {
678		display_no_controls();
679	}
680	display_scroll_indicators();
681}
682
683void compute_controls_layout(void)
684{
685	bool any_volume, any_pswitch, any_cswitch, any_multich;
686	int max_width, name_len;
687	int height, space;
688	unsigned int i;
689
690	if (controls_count == 0 || screen_too_small) {
691		visible_controls = 0;
692		return;
693	}
694
695	any_volume = FALSE;
696	any_pswitch = FALSE;
697	any_cswitch = FALSE;
698	any_multich = FALSE;
699	for (i = 0; i < controls_count; ++i) {
700		if (controls[i].flags & (TYPE_PVOLUME | TYPE_CVOLUME))
701			any_volume = 1;
702		if (controls[i].flags & TYPE_PSWITCH)
703			any_pswitch = 1;
704		if (controls[i].flags & TYPE_CSWITCH)
705			any_cswitch = 1;
706		if (controls[i].flags & IS_MULTICH)
707			any_multich = 1;
708	}
709
710	max_width = 8;
711	for (i = 0; i < controls_count; ++i) {
712		name_len = strlen(controls[i].name);
713		if (name_len > max_width)
714			max_width = name_len;
715	}
716	max_width = (max_width + 1) & ~1;
717
718	control_width = (screen_cols - 3 - (int)controls_count) / controls_count;
719	if (control_width < 8)
720		control_width = 8;
721	if (control_width > max_width)
722		control_width = max_width;
723	if (control_width > screen_cols - 4)
724		control_width = screen_cols - 4;
725
726	visible_controls = (screen_cols - 3) / (control_width + 1);
727	if (visible_controls > (int)controls_count)
728		visible_controls = controls_count;
729
730	first_control_x = 2 + (screen_cols - 3 - visible_controls * (control_width + 1)) / 2;
731
732	if (control_width < max_width)
733		control_name_width = control_width;
734	else
735		control_name_width = max_width;
736
737	height = 2;
738	if (any_volume)
739		height += 2;
740	if (any_pswitch)
741		height += 2;
742	if (any_cswitch)
743		height += 1;
744	if (any_multich)
745		height += 1;
746	if (any_volume) {
747		space = screen_lines - 6 - height;
748		if (space <= 1)
749			volume_height = 1;
750		else if (space <= 10)
751			volume_height = space;
752		else
753			volume_height = 10 + (space - 10) / 2;
754		height += volume_height;
755	}
756
757	space = screen_lines - 6 - height;
758	channel_name_y = screen_lines - 2 - space / 2;
759	name_y = channel_name_y - any_multich;
760	values_y = name_y - any_volume;
761	cswitch_y = values_y - any_cswitch;
762	base_y = cswitch_y - 1 - 2 * any_pswitch;
763}
764