Imported Upstream version 1.0.28
[platform/upstream/alsa-utils.git] / alsamixer / mixer_widget.c
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_controls.h"
38 #include "mixer_display.h"
39 #include "mixer_widget.h"
40
41 snd_mixer_t *mixer;
42 char *mixer_device_name;
43 bool unplugged;
44
45 struct widget mixer_widget;
46
47 enum view_mode view_mode;
48
49 int focus_control_index;
50 snd_mixer_selem_id_t *current_selem_id;
51 unsigned int current_control_flags;
52
53 bool control_values_changed;
54 bool controls_changed;
55
56 enum channel_mask {
57         LEFT = 1,
58         RIGHT = 2,
59 };
60
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
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
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(&current_selem_id);
105         if (err < 0)
106                 fatal_error("out of memory");
107 }
108
109 static void set_view_mode(enum view_mode m)
110 {
111         view_mode = m;
112         create_controls();
113 }
114
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
433 static void toggle_mute(unsigned int channels)
434 {
435         toggle_switches(TYPE_PSWITCH, channels);
436 }
437
438 static void toggle_capture(unsigned int channels)
439 {
440         toggle_switches(TYPE_CSWITCH, channels);
441 }
442
443 static void balance_volumes(void)
444 {
445         struct control *control;
446         double left, right;
447         int err;
448
449         control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME);
450         if (!control || !(control->flags & HAS_VOLUME_1))
451                 return;
452         if (control->flags & TYPE_PVOLUME) {
453                 left = get_normalized_playback_volume(control->elem, control->volume_channels[0]);
454                 right = get_normalized_playback_volume(control->elem, control->volume_channels[1]);
455         } else {
456                 left = get_normalized_capture_volume(control->elem, control->volume_channels[0]);
457                 right = get_normalized_capture_volume(control->elem, control->volume_channels[1]);
458         }
459         left = (left + right) / 2;
460         if (control->flags & TYPE_PVOLUME) {
461                 set_normalized_playback_volume(control->elem, control->volume_channels[0], left, 0);
462                 set_normalized_playback_volume(control->elem, control->volume_channels[1], left, 0);
463         } else {
464                 set_normalized_capture_volume(control->elem, control->volume_channels[0], left, 0);
465                 set_normalized_capture_volume(control->elem, control->volume_channels[1], left, 0);
466         }
467         display_controls();
468 }
469
470 static void on_handle_key(int key)
471 {
472         switch (key) {
473         case 27:
474         case KEY_CANCEL:
475         case KEY_F(10):
476                 mixer_widget.close();
477                 break;
478         case KEY_F(1):
479         case KEY_HELP:
480         case 'H':
481         case 'h':
482         case '?':
483                 show_help();
484                 break;
485         case KEY_F(2):
486         case '/':
487                 create_proc_files_list();
488                 break;
489         case KEY_F(3):
490                 set_view_mode(VIEW_MODE_PLAYBACK);
491                 break;
492         case KEY_F(4):
493                 set_view_mode(VIEW_MODE_CAPTURE);
494                 break;
495         case KEY_F(5):
496                 set_view_mode(VIEW_MODE_ALL);
497                 break;
498         case '\t':
499                 set_view_mode((enum view_mode)((view_mode + 1) % VIEW_MODE_COUNT));
500                 break;
501         case KEY_F(6):
502         case 'S':
503         case 's':
504                 create_card_select_list();
505                 break;
506         case KEY_REFRESH:
507         case 12:
508         case 'L':
509         case 'l':
510                 clearok(mixer_widget.window, TRUE);
511                 display_controls();
512                 break;
513         case KEY_LEFT:
514         case 'P':
515         case 'p':
516                 if (focus_control_index > 0) {
517                         --focus_control_index;
518                         refocus_control();
519                 }
520                 break;
521         case KEY_RIGHT:
522         case 'N':
523         case 'n':
524                 if (focus_control_index < controls_count - 1) {
525                         ++focus_control_index;
526                         refocus_control();
527                 }
528                 break;
529         case KEY_PPAGE:
530                 change_control_relative(5, LEFT | RIGHT);
531                 break;
532         case KEY_NPAGE:
533                 change_control_relative(-5, LEFT | RIGHT);
534                 break;
535 #if 0
536         case KEY_BEG:
537         case KEY_HOME:
538                 change_control_to_percent(100, LEFT | RIGHT);
539                 break;
540 #endif
541         case KEY_LL:
542         case KEY_END:
543                 change_control_to_percent(0, LEFT | RIGHT);
544                 break;
545         case KEY_UP:
546         case '+':
547         case 'K':
548         case 'k':
549         case 'W':
550         case 'w':
551                 change_control_relative(1, LEFT | RIGHT);
552                 break;
553         case KEY_DOWN:
554         case '-':
555         case 'J':
556         case 'j':
557         case 'X':
558         case 'x':
559                 change_control_relative(-1, LEFT | RIGHT);
560                 break;
561         case '0': case '1': case '2': case '3': case '4':
562         case '5': case '6': case '7': case '8': case '9':
563                 change_control_to_percent((key - '0') * 10, LEFT | RIGHT);
564                 break;
565         case 'Q':
566         case 'q':
567                 change_control_relative(1, LEFT);
568                 break;
569         case 'Y':
570         case 'y':
571         case 'Z':
572         case 'z':
573                 change_control_relative(-1, LEFT);
574                 break;
575         case 'E':
576         case 'e':
577                 change_control_relative(1, RIGHT);
578                 break;
579         case 'C':
580         case 'c':
581                 change_control_relative(-1, RIGHT);
582                 break;
583         case 'M':
584         case 'm':
585                 toggle_mute(LEFT | RIGHT);
586                 break;
587         case 'B':
588         case 'b':
589         case '=':
590                 balance_volumes();
591                 break;
592         case '<':
593         case ',':
594                 toggle_mute(LEFT);
595                 break;
596         case '>':
597         case '.':
598                 toggle_mute(RIGHT);
599                 break;
600         case ' ':
601                 toggle_capture(LEFT | RIGHT);
602                 break;
603         case KEY_IC:
604         case ';':
605                 toggle_capture(LEFT);
606                 break;
607         case KEY_DC:
608         case '\'':
609                 toggle_capture(RIGHT);
610                 break;
611         }
612 }
613
614 static void create(void)
615 {
616         static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " ";
617
618         widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0,
619                     attr_mixer_frame, WIDGET_BORDER);
620         if (screen_cols >= (sizeof(title) - 1) + 2) {
621                 wattrset(mixer_widget.window, attr_mixer_active);
622                 mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title);
623         }
624         init_mixer_layout();
625         display_card_info();
626         set_view_mode(view_mode);
627 }
628
629 static void on_window_size_changed(void)
630 {
631         create();
632 }
633
634 static void on_close(void)
635 {
636         widget_free(&mixer_widget);
637 }
638
639 void mixer_shutdown(void)
640 {
641         free_controls();
642         if (mixer)
643                 snd_mixer_close(mixer);
644         if (current_selem_id)
645                 snd_mixer_selem_id_free(current_selem_id);
646 }
647
648 struct widget mixer_widget = {
649         .handle_key = on_handle_key,
650         .window_size_changed = on_window_size_changed,
651         .close = on_close,
652 };
653
654 void create_mixer_widget(void)
655 {
656         create();
657 }