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