tarina

git clone https://git.tarina.org/tarina
Log | Files | Refs | README | LICENSE

mixer_display.c (22959B)


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