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>
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.
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.
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/>.
25
#include <alsa/asoundlib.h>
26
#include "gettext_curses.h"
31
#include "mixer_widget.h"
32
#include "mixer_controls.h"
33
#include "mixer_display.h"
41
static bool screen_too_small;
42
static bool has_info_items;
44
static int info_items_left;
45
static int info_items_width;
47
static int visible_controls;
48
static int first_visible_control_index;
49
static int first_control_x;
50
static int control_width;
51
static int control_name_width;
54
static int volume_height;
58
static int channel_name_y;
60
static void display_string_in_field(int y, int x, const char *s, int width, enum align align)
67
wmove(mixer_widget.window, y, x);
69
s_end = mbs_at_width(s, &string_width, -1);
70
if (string_width >= width) {
71
waddnstr(mixer_widget.window, s, s_end - s);
73
if (align != ALIGN_LEFT) {
74
spaces = width - string_width;
75
if (align == ALIGN_CENTER)
78
wprintw(mixer_widget.window, "%*s", spaces, "");
80
waddstr(mixer_widget.window, s);
81
if (align != ALIGN_RIGHT) {
82
getyx(mixer_widget.window, cur_y, cur_x);
84
spaces = x + width - cur_x;
86
wprintw(mixer_widget.window, "%*s", spaces, "");
92
void init_mixer_layout(void)
94
const char *labels_left[4] = {
100
const char *labels_right[4] = {
102
_("F2: System information"),
103
_("F6: Select sound card"),
106
unsigned int label_width_left, label_width_right;
107
unsigned int right_x, i;
109
screen_too_small = screen_lines < 14 || screen_cols < 12;
110
has_info_items = screen_lines >= 6;
114
label_width_left = get_max_mbs_width(labels_left, 4);
115
label_width_right = get_max_mbs_width(labels_right, 4);
116
if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols)
117
label_width_right = 0;
118
if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols)
119
label_width_left = 0;
121
info_items_left = label_width_left ? 3 + label_width_left : 2;
122
right_x = screen_cols - label_width_right - 2;
123
info_items_width = right_x - info_items_left;
124
if (info_items_width < 1) {
125
has_info_items = FALSE;
129
wattrset(mixer_widget.window, attr_mixer_text);
130
if (label_width_left)
131
for (i = 0; i < 4; ++i)
132
display_string_in_field(1 + i, 2, labels_left[i],
133
label_width_left, ALIGN_RIGHT);
134
if (label_width_right)
135
for (i = 0; i < 4; ++i)
136
display_string_in_field(1 + i, right_x, labels_right[i],
137
label_width_right, ALIGN_LEFT);
140
void display_card_info(void)
144
snd_ctl_card_info_t *card_info;
145
const char *card_name = NULL;
146
const char *mixer_name = NULL;
152
snd_ctl_card_info_alloca(&card_info);
153
if (mixer_device_name)
154
err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
158
ctl = snd_hctl_ctl(hctl);
159
err = snd_ctl_card_info(ctl, card_info);
161
card_name = snd_ctl_card_info_get_name(card_info);
162
mixer_name = snd_ctl_card_info_get_mixername(card_info);
167
wattrset(mixer_widget.window, attr_mixer_active);
169
wattrset(mixer_widget.window, attr_mixer_text);
171
card_name = _("(unplugged)");
175
display_string_in_field(1, info_items_left, card_name, info_items_width, ALIGN_LEFT);
178
wattrset(mixer_widget.window, attr_mixer_active);
180
wattrset(mixer_widget.window, attr_mixer_text);
183
display_string_in_field(2, info_items_left, mixer_name, info_items_width, ALIGN_LEFT);
186
void display_view_mode(void)
188
const char *modes[3] = {
193
unsigned int widths[3];
200
has_view_mode = controls_count > 0 || are_there_any_controls();
201
for (i = 0; i < 3; ++i)
202
widths[i] = get_mbs_width(modes[i]);
203
if (4 + widths[0] + 6 + widths[1] + 6 + widths[2] + 1 <= info_items_width) {
204
wmove(mixer_widget.window, 3, info_items_left);
205
wattrset(mixer_widget.window, attr_mixer_text);
206
for (i = 0; i < 3; ++i) {
207
wprintw(mixer_widget.window, "F%c:", '3' + i);
208
if (has_view_mode && (int)view_mode == i) {
209
wattrset(mixer_widget.window, attr_mixer_active);
210
wprintw(mixer_widget.window, "[%s]", modes[i]);
211
wattrset(mixer_widget.window, attr_mixer_text);
213
wprintw(mixer_widget.window, " %s ", modes[i]);
216
waddch(mixer_widget.window, ' ');
219
wattrset(mixer_widget.window, attr_mixer_active);
220
display_string_in_field(3, info_items_left,
221
has_view_mode ? modes[view_mode] : "",
222
info_items_width, ALIGN_LEFT);
226
static char *format_gain(long db)
228
if (db != SND_CTL_TLV_DB_GAIN_MUTE)
229
return casprintf("%.2f", db / 100.0);
231
return cstrdup(_("mute"));
234
static void display_focus_item_info(void)
236
struct control *control;
248
wattrset(mixer_widget.window, attr_mixer_active);
249
if (!controls_count || screen_too_small) {
250
display_string_in_field(4, info_items_left, "", info_items_width, ALIGN_LEFT);
253
control = &controls[focus_control_index];
255
if (control->flags & TYPE_ENUM) {
256
err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index);
258
err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf);
260
value_info = casprintf(" [%s]", buf);
261
} else if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
262
int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
264
if (control->flags & TYPE_PVOLUME)
265
get_vol_func = snd_mixer_selem_get_playback_dB;
267
get_vol_func = snd_mixer_selem_get_capture_dB;
268
if (!(control->flags & HAS_VOLUME_1)) {
269
err = get_vol_func(control->elem, control->volume_channels[0], &db);
271
dbs = format_gain(db);
272
value_info = casprintf(" [%s %s]", _("dB gain:"), dbs);
276
err = get_vol_func(control->elem, control->volume_channels[0], &db);
278
err = get_vol_func(control->elem, control->volume_channels[1], &db2);
280
dbs = format_gain(db);
281
dbs2 = format_gain(db2);
282
value_info = casprintf(_(" [%s %s, %s]"), _("dB gain:"), dbs, dbs2);
287
} else if (control->flags & TYPE_PSWITCH) {
288
if (!(control->flags & HAS_PSWITCH_1)) {
289
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw);
291
value_info = casprintf(" [%s]", _("Off"));
293
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw);
295
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &sw2);
296
if (err >= 0 && (!sw || !sw2))
297
value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off"));
299
} else if (control->flags & TYPE_CSWITCH) {
300
if (!(control->flags & HAS_CSWITCH_1)) {
301
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw);
303
value_info = casprintf(" [%s]", _("Off"));
305
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw);
307
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &sw2);
308
if (err >= 0 && (!sw || !sw2))
309
value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off"));
312
item_info = casprintf("%s%s", control->name, value_info ? value_info : "");
314
display_string_in_field(4, info_items_left, item_info, info_items_width, ALIGN_LEFT);
318
static void clear_controls_display(void)
322
wattrset(mixer_widget.window, attr_mixer_frame);
323
for (i = 5; i < screen_lines - 1; ++i)
324
mvwprintw(mixer_widget.window, i, 1, "%*s", screen_cols - 2, "");
327
static void center_string(int line, const char *s)
329
int width = get_mbs_width(s);
330
if (width <= screen_cols - 2)
331
mvwaddstr(mixer_widget.window, line, (screen_cols - width) / 2, s);
334
static void display_unplugged(void)
336
int lines, top, left;
339
lines = screen_lines - 6;
343
boojum = lines >= 10 && screen_cols >= 48;
344
top -= boojum ? 5 : 1;
348
left = (screen_cols - 46) / 2;
349
wattrset(mixer_widget.window, attr_mixer_text);
350
mvwaddstr(mixer_widget.window, top + 0, left, "In the midst of the word he was trying to say,");
351
mvwaddstr(mixer_widget.window, top + 1, left + 2, "In the midst of his laughter and glee,");
352
mvwaddstr(mixer_widget.window, top + 2, left, "He had softly and suddenly vanished away---");
353
mvwaddstr(mixer_widget.window, top + 3, left + 2, "For the Snark was a Boojum, you see.");
354
mvwchgat(mixer_widget.window, top + 3, left + 16, 3, /* ^^^ */
355
attr_mixer_text | A_BOLD, PAIR_NUMBER(attr_mixer_text), NULL);
356
mvwaddstr(mixer_widget.window, top + 5, left, "(Lewis Carroll, \"The Hunting of the Snark\")");
359
wattrset(mixer_widget.window, attr_errormsg);
360
center_string(top, _("The sound device was unplugged."));
361
center_string(top + 1, _("Press F6 to select another sound card."));
364
static void display_no_controls(void)
369
y = (screen_lines - 6) / 2 - 1;
372
if (y >= screen_lines - 1)
374
wattrset(mixer_widget.window, attr_infomsg);
375
if (view_mode == VIEW_MODE_PLAYBACK && are_there_any_controls())
376
msg = _("This sound device does not have any playback controls.");
377
else if (view_mode == VIEW_MODE_CAPTURE && are_there_any_controls())
378
msg = _("This sound device does not have any capture controls.");
380
msg = _("This sound device does not have any controls.");
381
center_string(y, msg);
384
static void display_string_centered_in_control(int y, int col, const char *s, int width)
388
left = first_control_x + col * (control_width + 1);
389
x = left + (control_width - width) / 2;
390
display_string_in_field(y, x, s, width, ALIGN_CENTER);
393
static void display_control(unsigned int control_index)
395
struct control *control;
398
int left, frame_left;
399
int bar_height, value;
408
control = &controls[control_index];
409
col = control_index - first_visible_control_index;
410
left = first_control_x + col * (control_width + 1);
411
frame_left = left + (control_width - 4) / 2;
412
if (control->flags & IS_ACTIVE)
413
wattrset(mixer_widget.window, attr_ctl_frame);
415
wattrset(mixer_widget.window, attr_ctl_inactive);
416
if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
417
mvwaddch(mixer_widget.window, base_y - volume_height - 1, frame_left, ACS_ULCORNER);
418
waddch(mixer_widget.window, ACS_HLINE);
419
waddch(mixer_widget.window, ACS_HLINE);
420
waddch(mixer_widget.window, ACS_URCORNER);
421
for (i = 0; i < volume_height; ++i) {
422
mvwaddch(mixer_widget.window, base_y - i - 1, frame_left, ACS_VLINE);
423
mvwaddch(mixer_widget.window, base_y - i - 1, frame_left + 3, ACS_VLINE);
425
mvwaddch(mixer_widget.window, base_y, frame_left,
426
control->flags & TYPE_PSWITCH ? ACS_LTEE : ACS_LLCORNER);
427
waddch(mixer_widget.window, ACS_HLINE);
428
waddch(mixer_widget.window, ACS_HLINE);
429
waddch(mixer_widget.window,
430
control->flags & TYPE_PSWITCH ? ACS_RTEE : ACS_LRCORNER);
431
} else if (control->flags & TYPE_PSWITCH) {
432
mvwaddch(mixer_widget.window, base_y, frame_left, ACS_ULCORNER);
433
waddch(mixer_widget.window, ACS_HLINE);
434
waddch(mixer_widget.window, ACS_HLINE);
435
waddch(mixer_widget.window, ACS_URCORNER);
437
if (control->flags & TYPE_PSWITCH) {
438
mvwaddch(mixer_widget.window, base_y + 1, frame_left, ACS_VLINE);
439
mvwaddch(mixer_widget.window, base_y + 1, frame_left + 3, ACS_VLINE);
440
mvwaddch(mixer_widget.window, base_y + 2, frame_left, ACS_LLCORNER);
441
waddch(mixer_widget.window, ACS_HLINE);
442
waddch(mixer_widget.window, ACS_HLINE);
443
waddch(mixer_widget.window, ACS_LRCORNER);
445
if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
446
int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
448
if (control->flags & TYPE_PVOLUME)
449
get_vol_func = snd_mixer_selem_get_playback_volume;
451
get_vol_func = snd_mixer_selem_get_capture_volume;
452
err = get_vol_func(control->elem, control->volume_channels[0], &volumes[0]);
453
if (err >= 0 && (control->flags & HAS_VOLUME_1))
454
err = get_vol_func(control->elem, control->volume_channels[1], &volumes[1]);
456
volumes[1] = volumes[0];
459
if (control->flags & TYPE_PVOLUME)
460
err = snd_mixer_selem_get_playback_volume_range(control->elem, &min, &max);
462
err = snd_mixer_selem_get_capture_volume_range(control->elem, &min, &max);
466
if (control->flags & IS_ACTIVE)
467
wattrset(mixer_widget.window, 0);
468
for (c = 0; c < 2; c++) {
469
bar_height = ((volumes[c] - min) * volume_height +
470
max - min - 1) / (max - min);
471
for (i = 0; i < volume_height; ++i) {
473
if (i + 1 > bar_height)
474
ch = ' ' | (control->flags & IS_ACTIVE ?
478
if (!(control->flags & IS_ACTIVE))
480
#ifdef TRICOLOR_VOLUME_BAR
481
else if (i > volume_height * 8 / 10)
482
ch |= attr_ctl_bar_hi;
483
else if (i > volume_height * 4 / 10)
484
ch |= attr_ctl_bar_mi;
487
ch |= attr_ctl_bar_lo;
489
mvwaddch(mixer_widget.window, base_y - i - 1,
490
frame_left + c + 1, ch);
493
if (control->flags & IS_ACTIVE)
494
wattrset(mixer_widget.window, attr_mixer_active);
495
value = ((volumes[0] - min) * 100 + (max - min) / 2) / (max - min);
496
if (!(control->flags & HAS_VOLUME_1)) {
497
sprintf(buf, "%d", value);
498
display_string_in_field(values_y, frame_left - 2, buf, 8, ALIGN_CENTER);
500
mvwprintw(mixer_widget.window, values_y, frame_left - 2, "%3d", value);
501
if (control->flags & IS_ACTIVE)
502
wattrset(mixer_widget.window, attr_ctl_frame);
503
waddstr(mixer_widget.window, "<>");
504
if (control->flags & IS_ACTIVE)
505
wattrset(mixer_widget.window, attr_mixer_active);
506
value = ((volumes[1] - min) * 100 + (max - min) / 2) / (max - min);
507
wprintw(mixer_widget.window, "%-3d", value);
511
if (control->flags & TYPE_PSWITCH) {
512
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &switches[0]);
513
if (err >= 0 && (control->flags & HAS_PSWITCH_1))
514
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &switches[1]);
516
switches[1] = switches[0];
519
if (control->flags & IS_ACTIVE)
520
wattrset(mixer_widget.window, 0);
521
mvwaddch(mixer_widget.window, base_y + 1, frame_left + 1,
523
/* TRANSLATORS: playback on; one character */
524
? _("O")[0] | (control->flags & IS_ACTIVE ? attr_ctl_nomute : 0)
525
/* TRANSLATORS: playback muted; one character */
526
: _("M")[0] | (control->flags & IS_ACTIVE ? attr_ctl_mute : 0));
527
waddch(mixer_widget.window,
529
? _("O")[0] | (control->flags & IS_ACTIVE ? attr_ctl_nomute : 0)
530
: _("M")[0] | (control->flags & IS_ACTIVE ? attr_ctl_mute : 0));
533
if (control->flags & TYPE_CSWITCH) {
534
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &switches[0]);
535
if (err >= 0 && (control->flags & HAS_CSWITCH_1))
536
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &switches[1]);
538
switches[1] = switches[0];
541
if (control->flags & IS_ACTIVE)
542
wattrset(mixer_widget.window, switches[0] ? attr_ctl_capture : attr_ctl_nocapture);
543
/* TRANSLATORS: "left"; no more than two characters */
544
display_string_in_field(cswitch_y - 1, frame_left - 2, switches[0] ? _("L") : "", 2, ALIGN_RIGHT);
545
if (control->flags & IS_ACTIVE)
546
wattrset(mixer_widget.window, switches[1] ? attr_ctl_capture : attr_ctl_nocapture);
547
/* TRANSLATORS: "right"; no more than two characters */
548
display_string_in_field(cswitch_y - 1, frame_left + 4, switches[1] ? _("R") : "", 2, ALIGN_LEFT);
549
/* TRANSLATORS: no more than eight characters */
551
if (switches[0] || switches[1]) {
552
if (control->flags & IS_ACTIVE)
553
wattrset(mixer_widget.window, attr_ctl_capture);
554
display_string_in_field(cswitch_y, frame_left - 2, s, 8, ALIGN_CENTER);
556
i = get_mbs_width(s);
561
if (control->flags & IS_ACTIVE)
562
wattrset(mixer_widget.window, attr_ctl_nocapture);
563
display_string_in_field(cswitch_y, frame_left - 2, buf, 8, ALIGN_CENTER);
567
if (control->flags & TYPE_ENUM) {
568
err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index);
571
err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf);
574
if (control->flags & IS_ACTIVE)
575
wattrset(mixer_widget.window, attr_mixer_active);
576
display_string_centered_in_control(base_y, col, buf, control_width);
579
if (control_index == focus_control_index) {
580
i = first_control_x + col * (control_width + 1) + (control_width - control_name_width) / 2;
581
wattrset(mixer_widget.window, attr_ctl_mark_focus);
582
mvwaddch(mixer_widget.window, name_y, i - 1, '<');
583
mvwaddch(mixer_widget.window, name_y, i + control_name_width, '>');
584
if (control->flags & IS_ACTIVE)
585
wattrset(mixer_widget.window, attr_ctl_label_focus);
587
wattrset(mixer_widget.window, attr_ctl_label_inactive);
589
if (control->flags & IS_ACTIVE)
590
wattrset(mixer_widget.window, attr_ctl_label);
592
wattrset(mixer_widget.window, attr_ctl_label_inactive);
594
display_string_centered_in_control(name_y, col, control->name, control_name_width);
595
if (channel_name_y > name_y) {
596
if (control->flags & IS_MULTICH) {
597
switch (control->flags & MULTICH_MASK) {
617
wattrset(mixer_widget.window, attr_mixer_frame);
619
display_string_centered_in_control(channel_name_y, col, s,
624
static void display_scroll_indicators(void)
629
if (screen_too_small)
631
y0 = screen_lines * 3 / 8;
632
y1 = screen_lines * 5 / 8;
633
left = first_visible_control_index > 0 ? ACS_LARROW : ACS_VLINE;
634
right = first_visible_control_index + visible_controls < controls_count
635
? ACS_RARROW : ACS_VLINE;
636
wattrset(mixer_widget.window, attr_mixer_frame);
637
for (y = y0; y <= y1; ++y) {
638
mvwaddch(mixer_widget.window, y, 0, left);
639
mvwaddch(mixer_widget.window, y, screen_cols - 1, right);
643
void display_controls(void)
647
if (first_visible_control_index > controls_count - visible_controls)
648
first_visible_control_index = controls_count - visible_controls;
649
if (first_visible_control_index > focus_control_index)
650
first_visible_control_index = focus_control_index;
651
else if (first_visible_control_index < focus_control_index - visible_controls + 1 && visible_controls)
652
first_visible_control_index = focus_control_index - visible_controls + 1;
654
clear_controls_display();
656
display_focus_item_info();
658
if (controls_count > 0) {
659
if (!screen_too_small)
660
for (i = 0; i < visible_controls; ++i)
661
display_control(first_visible_control_index + i);
662
} else if (unplugged) {
664
} else if (mixer_device_name) {
665
display_no_controls();
667
display_scroll_indicators();
668
controls_changed = FALSE;
671
void compute_controls_layout(void)
673
bool any_volume, any_pswitch, any_cswitch, any_multich;
674
int max_width, name_len;
678
if (controls_count == 0 || screen_too_small) {
679
visible_controls = 0;
687
for (i = 0; i < controls_count; ++i) {
688
if (controls[i].flags & (TYPE_PVOLUME | TYPE_CVOLUME))
690
if (controls[i].flags & TYPE_PSWITCH)
692
if (controls[i].flags & TYPE_CSWITCH)
694
if (controls[i].flags & IS_MULTICH)
699
for (i = 0; i < controls_count; ++i) {
700
name_len = strlen(controls[i].name);
701
if (name_len > max_width)
702
max_width = name_len;
704
max_width = (max_width + 1) & ~1;
706
control_width = (screen_cols - 3 - (int)controls_count) / controls_count;
707
if (control_width < 8)
709
if (control_width > max_width)
710
control_width = max_width;
711
if (control_width > screen_cols - 4)
712
control_width = screen_cols - 4;
714
visible_controls = (screen_cols - 3) / (control_width + 1);
715
if (visible_controls > controls_count)
716
visible_controls = controls_count;
718
first_control_x = 2 + (screen_cols - 3 - visible_controls * (control_width + 1)) / 2;
720
if (control_width < max_width)
721
control_name_width = control_width;
723
control_name_width = max_width;
735
space = screen_lines - 6 - height;
738
else if (space <= 10)
739
volume_height = space;
741
volume_height = 10 + (space - 10) / 2;
742
height += volume_height;
745
space = screen_lines - 6 - height;
746
channel_name_y = screen_lines - 2 - space / 2;
747
name_y = channel_name_y - any_multich;
748
values_y = name_y - any_volume;
749
cswitch_y = values_y - any_cswitch;
750
base_y = cswitch_y - 1 - 2 * any_pswitch;