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>
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.
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.
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/>.
25 #include <alsa/asoundlib.h>
26 #include "gettext_curses.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"
42 char *mixer_device_name;
45 struct widget mixer_widget;
47 enum view_mode view_mode;
49 int focus_control_index;
50 snd_mixer_selem_id_t *current_selem_id;
51 unsigned int current_control_flags;
53 bool control_values_changed;
54 bool controls_changed;
61 static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask)
63 if (mask == SND_CTL_EVENT_MASK_REMOVE) {
64 controls_changed = TRUE;
66 if (mask & SND_CTL_EVENT_MASK_VALUE)
67 control_values_changed = TRUE;
69 if (mask & SND_CTL_EVENT_MASK_INFO)
70 controls_changed = TRUE;
76 static int mixer_callback(snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem)
78 if (mask & SND_CTL_EVENT_MASK_ADD) {
79 snd_mixer_elem_set_callback(elem, elem_callback);
80 controls_changed = TRUE;
85 void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt)
89 err = snd_mixer_open(&mixer, 0);
91 fatal_alsa_error(_("cannot open mixer"), err);
93 mixer_device_name = cstrdup(selem_regopt->device);
94 err = snd_mixer_selem_register(mixer, selem_regopt, NULL);
96 fatal_alsa_error(_("cannot open mixer"), err);
98 snd_mixer_set_callback(mixer, mixer_callback);
100 err = snd_mixer_load(mixer);
102 fatal_alsa_error(_("cannot load mixer controls"), err);
104 err = snd_mixer_selem_id_malloc(¤t_selem_id);
106 fatal_error("out of memory");
109 static void set_view_mode(enum view_mode m)
115 static void close_hctl(void)
118 if (mixer_device_name) {
119 snd_mixer_detach(mixer, mixer_device_name);
120 free(mixer_device_name);
121 mixer_device_name = NULL;
125 static void check_unplugged(void)
133 if (mixer_device_name) {
134 err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
136 ctl = snd_hctl_ctl(hctl);
137 /* just any random function that does an ioctl() */
138 err = snd_ctl_get_power_state(ctl, &state);
145 void close_mixer_device(void)
151 set_view_mode(view_mode);
154 bool select_card_by_name(const char *device_name)
165 err = snd_mixer_attach(mixer, device_name);
169 msg = casprintf(_("Cannot open mixer device '%s'."), device_name);
170 show_alsa_error(msg, err);
175 mixer_device_name = cstrdup(device_name);
177 err = snd_mixer_load(mixer);
179 fatal_alsa_error(_("cannot load mixer controls"), err);
183 set_view_mode(view_mode);
187 static void show_help(void)
189 const char *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"),
200 _("Left Move to the previous control"),
201 _("Right Move to the next control"),
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"),
214 /* TRANSLATORS: or , . */
215 _("< > Toggle left/right mute"),
217 _("Space Toggle capture"),
218 /* TRANSLATORS: or Insert Delete */
219 _("; ' Toggle left/right capture"),
223 _(" Jaroslav Kysela <perex@perex.cz>"),
224 _(" Clemens Ladisch <clemens@ladisch.de>"),
226 show_text(help, ARRAY_SIZE(help), _("Help"));
229 void refocus_control(void)
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;
239 static struct control *get_focus_control(unsigned int type)
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];
250 static void change_enum_to_percent(struct control *control, int value)
254 unsigned int new_index;
258 i = ffs(control->enum_channel_bits) - 1;
259 err = snd_mixer_selem_get_enum_item(control->elem, i, &index);
265 } else if (value == 100) {
266 items = snd_mixer_selem_get_enum_items(control->elem);
269 new_index = items - 1;
271 if (new_index == index)
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);
278 static void change_enum_relative(struct control *control, int delta)
286 items = snd_mixer_selem_get_enum_items(control->elem);
289 err = snd_mixer_selem_get_enum_item(control->elem, 0, &index);
292 new_index = (int)index + delta;
295 else if (new_index >= items)
296 new_index = items - 1;
297 if (new_index == index)
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);
304 static void change_volume_to_percent(struct control *control, int value, unsigned int channels)
306 int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int);
308 if (!(control->flags & HAS_VOLUME_1))
310 if (control->flags & TYPE_PVOLUME)
311 set_func = set_normalized_playback_volume;
313 set_func = set_normalized_capture_volume;
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);
320 static double clamp_volume(double v)
329 static void change_volume_relative(struct control *control, int delta, unsigned int channels)
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);
336 if (!(control->flags & HAS_VOLUME_1))
338 if (control->flags & TYPE_PVOLUME) {
339 get_func = get_normalized_playback_volume;
340 set_func = set_normalized_playback_volume;
342 get_func = get_normalized_capture_volume;
343 set_func = set_normalized_capture_volume;
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);
354 if (channels & RIGHT) {
355 right = clamp_volume(right + delta / 100.0);
356 set_func(control->elem, control->volume_channels[1], right, dir);
360 static void change_control_to_percent(int value, unsigned int channels)
362 struct control *control;
364 control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
367 if (control->flags & TYPE_ENUM)
368 change_enum_to_percent(control, value);
370 change_volume_to_percent(control, value, channels);
374 static void change_control_relative(int delta, unsigned int channels)
376 struct control *control;
378 control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
381 if (control->flags & TYPE_ENUM)
382 change_enum_relative(control, delta);
384 change_volume_relative(control, delta, channels);
388 static void toggle_switches(unsigned int type, unsigned int channels)
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];
398 control = get_focus_control(type);
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];
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];
414 if (!(control->flags & switch_1_mask))
416 if (channels & LEFT) {
417 err = get_func(control->elem, channel_ids[0], &left);
421 if (channels & RIGHT) {
422 err = get_func(control->elem, channel_ids[1], &right);
427 set_func(control->elem, channel_ids[0], !left);
428 if (channels & RIGHT)
429 set_func(control->elem, channel_ids[1], !right);
433 static void toggle_mute(unsigned int channels)
435 toggle_switches(TYPE_PSWITCH, channels);
438 static void toggle_capture(unsigned int channels)
440 toggle_switches(TYPE_CSWITCH, channels);
443 static void balance_volumes(void)
445 struct control *control;
449 control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME);
450 if (!control || !(control->flags & HAS_VOLUME_1))
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]);
456 left = get_normalized_capture_volume(control->elem, control->volume_channels[0]);
457 right = get_normalized_capture_volume(control->elem, control->volume_channels[1]);
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);
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);
470 static void on_handle_key(int key)
476 mixer_widget.close();
487 create_proc_files_list();
490 set_view_mode(VIEW_MODE_PLAYBACK);
493 set_view_mode(VIEW_MODE_CAPTURE);
496 set_view_mode(VIEW_MODE_ALL);
499 set_view_mode((enum view_mode)((view_mode + 1) % VIEW_MODE_COUNT));
504 create_card_select_list();
510 clearok(mixer_widget.window, TRUE);
516 if (focus_control_index > 0) {
517 --focus_control_index;
524 if (focus_control_index < controls_count - 1) {
525 ++focus_control_index;
530 change_control_relative(5, LEFT | RIGHT);
533 change_control_relative(-5, LEFT | RIGHT);
538 change_control_to_percent(100, LEFT | RIGHT);
543 change_control_to_percent(0, LEFT | RIGHT);
551 change_control_relative(1, LEFT | RIGHT);
559 change_control_relative(-1, LEFT | RIGHT);
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);
567 change_control_relative(1, LEFT);
573 change_control_relative(-1, LEFT);
577 change_control_relative(1, RIGHT);
581 change_control_relative(-1, RIGHT);
585 toggle_mute(LEFT | RIGHT);
601 toggle_capture(LEFT | RIGHT);
605 toggle_capture(LEFT);
609 toggle_capture(RIGHT);
614 static void create(void)
616 static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " ";
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);
626 set_view_mode(view_mode);
629 static void on_window_size_changed(void)
634 static void on_close(void)
636 widget_free(&mixer_widget);
639 void mixer_shutdown(void)
643 snd_mixer_close(mixer);
644 if (current_selem_id)
645 snd_mixer_selem_id_free(current_selem_id);
648 struct widget mixer_widget = {
649 .handle_key = on_handle_key,
650 .window_size_changed = on_window_size_changed,
654 void create_mixer_widget(void)