Imported Upstream version 1.0.28
[platform/upstream/alsa-utils.git] / alsamixer / mixer_display.c
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 }