• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
39 enum align {
40 	ALIGN_LEFT,
41 	ALIGN_RIGHT,
42 	ALIGN_CENTER,
43 };
44 
45 static bool screen_too_small;
46 static bool has_info_items;
47 
48 static int info_items_left;
49 static int info_items_width;
50 
51 static int visible_controls;
52 static int first_visible_control_index;
53 static int first_control_x;
54 static int control_width;
55 static int control_name_width;
56 
57 static int base_y;
58 static int volume_height;
59 static int cswitch_y;
60 static int values_y;
61 static int name_y;
62 static int channel_name_y;
63 
display_string_in_field(int y,int x,const char * s,int width,enum align align)64 static 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 
init_mixer_layout(void)96 void 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 	unsigned int label_width_left, label_width_right;
111 	unsigned 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 
display_card_info(void)148 void 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 
display_view_mode(void)194 void display_view_mode(void)
195 {
196 	const char *modes[3] = {
197 		_("Playback"),
198 		_("Capture"),
199 		_("All"),
200 	};
201 	unsigned 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 
format_gain(long db)235 static 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 
display_focus_item_info(void)243 static 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 
clear_controls_display(void)327 static 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 
center_string(int line,const char * s)337 static 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 
display_unplugged(void)344 static 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 
display_no_controls(void)374 static 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 
display_string_centered_in_control(int y,int col,const char * s,int width)394 static 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 
display_control(unsigned int control_index)403 static 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 (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 
display_scroll_indicators(void)635 static 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 < 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 
display_controls(void)656 void display_controls(void)
657 {
658 	unsigned int i;
659 
660 	if (first_visible_control_index > 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 
compute_controls_layout(void)683 void 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 > 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