1 /*
2 * mixer_widget.c - mixer widget and keys handling
3 * Copyright (c) 1998,1999 Tim Janik
4 * Jaroslav Kysela <perex@perex.cz>
5 * Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "aconfig.h"
22 #include <stdlib.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <alsa/asoundlib.h>
26 #include "gettext_curses.h"
27 #include "version.h"
28 #include "utils.h"
29 #include "die.h"
30 #include "mem.h"
31 #include "colors.h"
32 #include "widget.h"
33 #include "textbox.h"
34 #include "proc_files.h"
35 #include "card_select.h"
36 #include "volume_mapping.h"
37 #include "mixer_clickable.h"
38 #include "mixer_controls.h"
39 #include "mixer_display.h"
40 #include "mixer_widget.h"
41 #include "bindings.h"
42
43 snd_mixer_t *mixer;
44 char *mixer_device_name;
45 bool unplugged;
46
47 struct widget mixer_widget;
48
49 enum view_mode view_mode;
50
51 int focus_control_index;
52 snd_mixer_selem_id_t *current_selem_id;
53 unsigned int current_control_flags;
54
55 bool control_values_changed;
56 bool controls_changed;
57
58 unsigned int mouse_wheel_step = 1;
59 bool mouse_wheel_focuses_control = 1;
60
elem_callback(snd_mixer_elem_t * elem,unsigned int mask)61 static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask)
62 {
63 if (mask == SND_CTL_EVENT_MASK_REMOVE) {
64 controls_changed = TRUE;
65 } else {
66 if (mask & SND_CTL_EVENT_MASK_VALUE)
67 control_values_changed = TRUE;
68
69 if (mask & SND_CTL_EVENT_MASK_INFO)
70 controls_changed = TRUE;
71 }
72
73 return 0;
74 }
75
mixer_callback(snd_mixer_t * mixer,unsigned int mask,snd_mixer_elem_t * elem)76 static int mixer_callback(snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem)
77 {
78 if (mask & SND_CTL_EVENT_MASK_ADD) {
79 snd_mixer_elem_set_callback(elem, elem_callback);
80 controls_changed = TRUE;
81 }
82 return 0;
83 }
84
create_mixer_object(struct snd_mixer_selem_regopt * selem_regopt)85 void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt)
86 {
87 int err;
88
89 err = snd_mixer_open(&mixer, 0);
90 if (err < 0)
91 fatal_alsa_error(_("cannot open mixer"), err);
92
93 mixer_device_name = cstrdup(selem_regopt->device);
94 err = snd_mixer_selem_register(mixer, selem_regopt, NULL);
95 if (err < 0)
96 fatal_alsa_error(_("cannot open mixer"), err);
97
98 snd_mixer_set_callback(mixer, mixer_callback);
99
100 err = snd_mixer_load(mixer);
101 if (err < 0)
102 fatal_alsa_error(_("cannot load mixer controls"), err);
103
104 err = snd_mixer_selem_id_malloc(¤t_selem_id);
105 if (err < 0)
106 fatal_error("out of memory");
107 }
108
set_view_mode(enum view_mode m)109 static void set_view_mode(enum view_mode m)
110 {
111 view_mode = m;
112 create_controls();
113 }
114
close_hctl(void)115 static void close_hctl(void)
116 {
117 free_controls();
118 if (mixer_device_name) {
119 snd_mixer_detach(mixer, mixer_device_name);
120 free(mixer_device_name);
121 mixer_device_name = NULL;
122 }
123 }
124
check_unplugged(void)125 static void check_unplugged(void)
126 {
127 snd_hctl_t *hctl;
128 snd_ctl_t *ctl;
129 unsigned int state;
130 int err;
131
132 unplugged = FALSE;
133 if (mixer_device_name) {
134 err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
135 if (err >= 0) {
136 ctl = snd_hctl_ctl(hctl);
137 /* just any random function that does an ioctl() */
138 err = snd_ctl_get_power_state(ctl, &state);
139 if (err == -ENODEV)
140 unplugged = TRUE;
141 }
142 }
143 }
144
close_mixer_device(void)145 void close_mixer_device(void)
146 {
147 check_unplugged();
148 close_hctl();
149
150 display_card_info();
151 set_view_mode(view_mode);
152 }
153
select_card_by_name(const char * device_name)154 bool select_card_by_name(const char *device_name)
155 {
156 int err;
157 bool opened;
158 char *msg;
159
160 close_hctl();
161 unplugged = FALSE;
162
163 opened = FALSE;
164 if (device_name) {
165 err = snd_mixer_attach(mixer, device_name);
166 if (err >= 0)
167 opened = TRUE;
168 else {
169 msg = casprintf(_("Cannot open mixer device '%s'."), device_name);
170 show_alsa_error(msg, err);
171 free(msg);
172 }
173 }
174 if (opened) {
175 mixer_device_name = cstrdup(device_name);
176
177 err = snd_mixer_load(mixer);
178 if (err < 0)
179 fatal_alsa_error(_("cannot load mixer controls"), err);
180 }
181
182 display_card_info();
183 set_view_mode(view_mode);
184 return opened;
185 }
186
show_help(void)187 static void show_help(void)
188 {
189 const char *help[] = {
190 _("Esc Exit"),
191 _("F1 ? H Help"),
192 _("F2 / System information"),
193 _("F3 Show playback controls"),
194 _("F4 Show capture controls"),
195 _("F5 Show all controls"),
196 _("Tab Toggle view mode (F3/F4/F5)"),
197 _("F6 S Select sound card"),
198 _("L Redraw screen"),
199 "",
200 _("Left Move to the previous control"),
201 _("Right Move to the next control"),
202 "",
203 _("Up/Down Change volume"),
204 _("+ - Change volume"),
205 _("Page Up/Dn Change volume in big steps"),
206 _("End Set volume to 0%"),
207 _("0-9 Set volume to 0%-90%"),
208 _("Q W E Increase left/both/right volumes"),
209 /* TRANSLATORS: or Y instead of Z */
210 _("Z X C Decrease left/both/right volumes"),
211 _("B Balance left and right volumes"),
212 "",
213 _("M Toggle mute"),
214 /* TRANSLATORS: or , . */
215 _("< > Toggle left/right mute"),
216 "",
217 _("Space Toggle capture"),
218 /* TRANSLATORS: or Insert Delete */
219 _("; ' Toggle left/right capture"),
220 "",
221 _("Authors:"),
222 _(" Tim Janik"),
223 _(" Jaroslav Kysela <perex@perex.cz>"),
224 _(" Clemens Ladisch <clemens@ladisch.de>"),
225 };
226 show_text(help, ARRAY_SIZE(help), _("Help"));
227 }
228
refocus_control(void)229 void refocus_control(void)
230 {
231 if (focus_control_index < controls_count) {
232 snd_mixer_selem_get_id(controls[focus_control_index].elem, current_selem_id);
233 current_control_flags = controls[focus_control_index].flags;
234 }
235
236 display_controls();
237 }
238
get_focus_control(unsigned int type)239 static struct control *get_focus_control(unsigned int type)
240 {
241 if (focus_control_index >= 0 &&
242 focus_control_index < controls_count &&
243 (controls[focus_control_index].flags & IS_ACTIVE) &&
244 (controls[focus_control_index].flags & type))
245 return &controls[focus_control_index];
246 else
247 return NULL;
248 }
249
change_enum_to_percent(struct control * control,int value)250 static void change_enum_to_percent(struct control *control, int value)
251 {
252 unsigned int i;
253 unsigned int index;
254 unsigned int new_index;
255 int items;
256 int err;
257
258 i = ffs(control->enum_channel_bits) - 1;
259 err = snd_mixer_selem_get_enum_item(control->elem, i, &index);
260 if (err < 0)
261 return;
262 new_index = index;
263 if (value == 0) {
264 new_index = 0;
265 } else if (value == 100) {
266 items = snd_mixer_selem_get_enum_items(control->elem);
267 if (items < 1)
268 return;
269 new_index = items - 1;
270 }
271 if (new_index == index)
272 return;
273 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
274 if (control->enum_channel_bits & (1 << i))
275 snd_mixer_selem_set_enum_item(control->elem, i, new_index);
276 }
277
change_enum_relative(struct control * control,int delta)278 static void change_enum_relative(struct control *control, int delta)
279 {
280 int items;
281 unsigned int i;
282 unsigned int index;
283 int new_index;
284 int err;
285
286 items = snd_mixer_selem_get_enum_items(control->elem);
287 if (items < 1)
288 return;
289 err = snd_mixer_selem_get_enum_item(control->elem, 0, &index);
290 if (err < 0)
291 return;
292 new_index = (int)index + delta;
293 if (new_index < 0)
294 new_index = 0;
295 else if (new_index >= items)
296 new_index = items - 1;
297 if (new_index == index)
298 return;
299 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
300 if (control->enum_channel_bits & (1 << i))
301 snd_mixer_selem_set_enum_item(control->elem, i, new_index);
302 }
303
change_volume_to_percent(struct control * control,int value,unsigned int channels)304 static void change_volume_to_percent(struct control *control, int value, unsigned int channels)
305 {
306 int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int);
307
308 if (!(control->flags & HAS_VOLUME_1))
309 channels = LEFT;
310 if (control->flags & TYPE_PVOLUME)
311 set_func = set_normalized_playback_volume;
312 else
313 set_func = set_normalized_capture_volume;
314 if (channels & LEFT)
315 set_func(control->elem, control->volume_channels[0], value / 100.0, 0);
316 if (channels & RIGHT)
317 set_func(control->elem, control->volume_channels[1], value / 100.0, 0);
318 }
319
clamp_volume(double v)320 static double clamp_volume(double v)
321 {
322 if (v < 0)
323 return 0;
324 if (v > 1)
325 return 1;
326 return v;
327 }
328
change_volume_relative(struct control * control,int delta,unsigned int channels)329 static void change_volume_relative(struct control *control, int delta, unsigned int channels)
330 {
331 double (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t);
332 int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int);
333 double left, right;
334 int dir;
335
336 if (!(control->flags & HAS_VOLUME_1))
337 channels = LEFT;
338 if (control->flags & TYPE_PVOLUME) {
339 get_func = get_normalized_playback_volume;
340 set_func = set_normalized_playback_volume;
341 } else {
342 get_func = get_normalized_capture_volume;
343 set_func = set_normalized_capture_volume;
344 }
345 if (channels & LEFT)
346 left = get_func(control->elem, control->volume_channels[0]);
347 if (channels & RIGHT)
348 right = get_func(control->elem, control->volume_channels[1]);
349 dir = delta > 0 ? 1 : -1;
350 if (channels & LEFT) {
351 left = clamp_volume(left + delta / 100.0);
352 set_func(control->elem, control->volume_channels[0], left, dir);
353 }
354 if (channels & RIGHT) {
355 right = clamp_volume(right + delta / 100.0);
356 set_func(control->elem, control->volume_channels[1], right, dir);
357 }
358 }
359
change_control_to_percent(int value,unsigned int channels)360 static void change_control_to_percent(int value, unsigned int channels)
361 {
362 struct control *control;
363
364 control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
365 if (!control)
366 return;
367 if (control->flags & TYPE_ENUM)
368 change_enum_to_percent(control, value);
369 else
370 change_volume_to_percent(control, value, channels);
371 display_controls();
372 }
373
change_control_relative(int delta,unsigned int channels)374 static void change_control_relative(int delta, unsigned int channels)
375 {
376 struct control *control;
377
378 control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
379 if (!control)
380 return;
381 if (control->flags & TYPE_ENUM)
382 change_enum_relative(control, delta);
383 else
384 change_volume_relative(control, delta, channels);
385 display_controls();
386 }
387
toggle_switches(unsigned int type,unsigned int channels)388 static void toggle_switches(unsigned int type, unsigned int channels)
389 {
390 struct control *control;
391 unsigned int switch_1_mask;
392 int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *);
393 int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int);
394 snd_mixer_selem_channel_id_t channel_ids[2];
395 int left, right;
396 int err;
397
398 control = get_focus_control(type);
399 if (!control)
400 return;
401 if (type == TYPE_PSWITCH) {
402 switch_1_mask = HAS_PSWITCH_1;
403 get_func = snd_mixer_selem_get_playback_switch;
404 set_func = snd_mixer_selem_set_playback_switch;
405 channel_ids[0] = control->pswitch_channels[0];
406 channel_ids[1] = control->pswitch_channels[1];
407 } else {
408 switch_1_mask = HAS_CSWITCH_1;
409 get_func = snd_mixer_selem_get_capture_switch;
410 set_func = snd_mixer_selem_set_capture_switch;
411 channel_ids[0] = control->cswitch_channels[0];
412 channel_ids[1] = control->cswitch_channels[1];
413 }
414 if (!(control->flags & switch_1_mask))
415 channels = LEFT;
416 if (channels & LEFT) {
417 err = get_func(control->elem, channel_ids[0], &left);
418 if (err < 0)
419 return;
420 }
421 if (channels & RIGHT) {
422 err = get_func(control->elem, channel_ids[1], &right);
423 if (err < 0)
424 return;
425 }
426 if (channels & LEFT)
427 set_func(control->elem, channel_ids[0], !left);
428 if (channels & RIGHT)
429 set_func(control->elem, channel_ids[1], !right);
430 display_controls();
431 }
432
toggle_mute(unsigned int channels)433 static void toggle_mute(unsigned int channels)
434 {
435 toggle_switches(TYPE_PSWITCH, channels);
436 }
437
toggle_capture(unsigned int channels)438 static void toggle_capture(unsigned int channels)
439 {
440 toggle_switches(TYPE_CSWITCH, channels);
441 }
442
balance_volumes(void)443 static void balance_volumes(void)
444 {
445 struct control *control;
446 double left, right;
447
448 control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME);
449 if (!control || !(control->flags & HAS_VOLUME_1))
450 return;
451 if (control->flags & TYPE_PVOLUME) {
452 left = get_normalized_playback_volume(control->elem, control->volume_channels[0]);
453 right = get_normalized_playback_volume(control->elem, control->volume_channels[1]);
454 } else {
455 left = get_normalized_capture_volume(control->elem, control->volume_channels[0]);
456 right = get_normalized_capture_volume(control->elem, control->volume_channels[1]);
457 }
458 left = (left + right) / 2;
459 if (control->flags & TYPE_PVOLUME) {
460 set_normalized_playback_volume(control->elem, control->volume_channels[0], left, 0);
461 set_normalized_playback_volume(control->elem, control->volume_channels[1], left, 0);
462 } else {
463 set_normalized_capture_volume(control->elem, control->volume_channels[0], left, 0);
464 set_normalized_capture_volume(control->elem, control->volume_channels[1], left, 0);
465 }
466 display_controls();
467 }
468
on_mouse_key()469 static int on_mouse_key() {
470 MEVENT m;
471 command_enum cmd = 0;
472 unsigned int channels = LEFT | RIGHT;
473 unsigned int index;
474 struct control *control;
475 struct clickable_rect *rect;
476
477 if (getmouse(&m) == ERR)
478 return 0;
479
480 if (m.bstate & (
481 BUTTON1_PRESSED|BUTTON1_RELEASED|
482 BUTTON2_PRESSED|BUTTON2_RELEASED|
483 BUTTON3_PRESSED|BUTTON3_RELEASED))
484 return 0;
485
486 rect = clickable_find(m.y, m.x);
487 if (rect)
488 cmd = rect->command;
489
490 #if NCURSES_MOUSE_VERSION > 1
491 if (m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED|BUTTON5_CLICKED|BUTTON5_PRESSED)) {
492 switch (cmd) {
493 case CMD_MIXER_MOUSE_CLICK_CONTROL_ENUM:
494 focus_control_index = rect->arg1;
495 return CMD_WITH_ARG((
496 m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED)
497 ? CMD_MIXER_CONTROL_UP
498 : CMD_MIXER_CONTROL_DOWN
499 ), 1);
500
501 case CMD_MIXER_MOUSE_CLICK_VOLUME_BAR:
502 if (mouse_wheel_focuses_control)
503 focus_control_index = rect->arg1;
504
505 default:
506 return CMD_WITH_ARG((
507 m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED)
508 ? CMD_MIXER_CONTROL_UP
509 : CMD_MIXER_CONTROL_DOWN
510 ), mouse_wheel_step);
511 }
512 }
513 #endif
514
515 /* If the rectangle has got an argument (value != -1) it is used for
516 * setting `focus_control_index` */
517 if (rect && rect->arg1 >= 0)
518 focus_control_index = rect->arg1;
519
520 switch (cmd) {
521 case CMD_MIXER_MOUSE_CLICK_VOLUME_BAR:
522 if (m.bstate & (BUTTON3_CLICKED|BUTTON3_DOUBLE_CLICKED|BUTTON3_TRIPLE_CLICKED))
523 channels = m.x - rect->x1 + 1;
524 return CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT_LEFT + channels - 1,
525 (100 * (rect->y2 - m.y) / (rect->y2 - rect->y1)) // volume
526 );
527
528 case CMD_MIXER_MOUSE_CLICK_MUTE:
529 if (m.bstate & (BUTTON3_CLICKED|BUTTON3_DOUBLE_CLICKED|BUTTON3_TRIPLE_CLICKED))
530 channels = m.x - rect->x1 + 1;
531 return CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, channels);
532
533 case CMD_MIXER_MOUSE_CLICK_CONTROL_ENUM:
534 control = get_focus_control(TYPE_ENUM);
535 if (control &&
536 (snd_mixer_selem_get_enum_item(control->elem, 0, &index) >= 0)) {
537 return (index == 0
538 ? CMD_WITH_ARG(CMD_MIXER_CONTROL_UP, 100)
539 : CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN, 1));
540 }
541 break;
542
543 default:
544 return cmd; // non-mouse command
545 }
546
547 return 0; // failed mouse command
548 }
549
on_handle_key(int key)550 static void on_handle_key(int key)
551 {
552 int arg;
553 command_enum cmd;
554
555 if (key == KEY_MOUSE)
556 cmd = on_mouse_key();
557 else if (key < ARRAY_SIZE(mixer_bindings))
558 cmd = mixer_bindings[key];
559 else
560 return;
561
562 arg = CMD_GET_ARGUMENT(cmd);
563 cmd = CMD_GET_COMMAND(cmd);
564
565 switch (cmd) {
566 case CMD_MIXER_CONTROL_DOWN_LEFT:
567 case CMD_MIXER_CONTROL_DOWN_RIGHT:
568 case CMD_MIXER_CONTROL_DOWN:
569 arg = (-arg);
570 case CMD_MIXER_CONTROL_UP_LEFT:
571 case CMD_MIXER_CONTROL_UP_RIGHT:
572 case CMD_MIXER_CONTROL_UP:
573 change_control_relative(arg, cmd % 4);
574 break;
575 case CMD_MIXER_CONTROL_SET_PERCENT_LEFT:
576 case CMD_MIXER_CONTROL_SET_PERCENT_RIGHT:
577 case CMD_MIXER_CONTROL_SET_PERCENT:
578 change_control_to_percent(arg, cmd % 4);
579 break;
580 case CMD_MIXER_CLOSE:
581 mixer_widget.close();
582 break;
583 case CMD_MIXER_HELP:
584 show_help();
585 break;
586 case CMD_MIXER_SYSTEM_INFORMATION:
587 create_proc_files_list();
588 break;
589 case CMD_MIXER_TOGGLE_VIEW_MODE:
590 arg = (view_mode + 1) % VIEW_MODE_COUNT;
591 case CMD_MIXER_SET_VIEW_MODE:
592 set_view_mode((enum view_mode)(arg));
593 break;
594 case CMD_MIXER_SELECT_CARD:
595 create_card_select_list();
596 break;
597 case CMD_MIXER_REFRESH:
598 clearok(mixer_widget.window, TRUE);
599 display_controls();
600 break;
601 case CMD_MIXER_PREVIOUS:
602 arg = (-arg);
603 case CMD_MIXER_NEXT:
604 arg = focus_control_index + arg;
605 case CMD_MIXER_FOCUS_CONTROL:
606 focus_control_index = arg;
607 if (focus_control_index < 0)
608 focus_control_index = 0;
609 else if (focus_control_index >= controls_count)
610 focus_control_index = controls_count - 1;
611 refocus_control();
612 break;
613 case CMD_MIXER_TOGGLE_MUTE:
614 toggle_mute(arg);
615 break;
616 case CMD_MIXER_TOGGLE_CAPTURE:
617 toggle_capture(arg);
618 break;
619 case CMD_MIXER_BALANCE_CONTROL:
620 balance_volumes();
621 break;
622 }
623 }
624
create(void)625 static void create(void)
626 {
627 static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " ";
628
629 widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0,
630 attrs.mixer_frame, WIDGET_BORDER);
631 if (screen_cols >= (sizeof(title) - 1) + 2) {
632 wattrset(mixer_widget.window, attrs.mixer_active);
633 mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title);
634 }
635 init_mixer_layout();
636 display_card_info();
637 set_view_mode(view_mode);
638 }
639
on_window_size_changed(void)640 static void on_window_size_changed(void)
641 {
642 create();
643 }
644
on_close(void)645 static void on_close(void)
646 {
647 widget_free(&mixer_widget);
648 }
649
mixer_shutdown(void)650 void mixer_shutdown(void)
651 {
652 free_controls();
653 if (mixer)
654 snd_mixer_close(mixer);
655 if (current_selem_id)
656 snd_mixer_selem_id_free(current_selem_id);
657 }
658
659 struct widget mixer_widget = {
660 .handle_key = on_handle_key,
661 .window_size_changed = on_window_size_changed,
662 .close = on_close,
663 };
664
create_mixer_widget(void)665 void create_mixer_widget(void)
666 {
667 create();
668 }
669