2
* mixer_widget.c - mixer widget and keys handling
3
* Copyright (c) 1998,1999 Tim Janik <timj@gtk.org>
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 "mixer_controls.h"
37
#include "mixer_display.h"
38
#include "mixer_widget.h"
41
char *mixer_device_name;
44
struct widget mixer_widget;
46
enum view_mode view_mode;
48
int focus_control_index;
49
snd_mixer_selem_id_t *current_selem_id;
50
unsigned int current_control_flags;
52
bool controls_changed;
59
static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask)
61
if (mask & (SND_CTL_EVENT_MASK_REMOVE |
62
SND_CTL_EVENT_MASK_INFO |
63
SND_CTL_EVENT_MASK_VALUE))
64
controls_changed = TRUE;
68
static int mixer_callback(snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem)
70
if (mask & SND_CTL_EVENT_MASK_ADD) {
71
snd_mixer_elem_set_callback(elem, elem_callback);
72
controls_changed = TRUE;
77
void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt)
81
err = snd_mixer_open(&mixer, 0);
83
fatal_alsa_error(_("cannot open mixer"), err);
85
mixer_device_name = cstrdup(selem_regopt->device);
86
err = snd_mixer_selem_register(mixer, selem_regopt, NULL);
88
fatal_alsa_error(_("cannot open mixer"), err);
90
snd_mixer_set_callback(mixer, mixer_callback);
92
err = snd_mixer_load(mixer);
94
fatal_alsa_error(_("cannot load mixer controls"), err);
96
err = snd_mixer_selem_id_malloc(¤t_selem_id);
98
fatal_error("out of memory");
101
static void set_view_mode(enum view_mode m)
107
static void close_hctl(void)
110
if (mixer_device_name) {
111
snd_mixer_detach(mixer, mixer_device_name);
112
free(mixer_device_name);
113
mixer_device_name = NULL;
117
static void check_unplugged(void)
125
if (mixer_device_name) {
126
err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
128
ctl = snd_hctl_ctl(hctl);
129
/* just any random function that does an ioctl() */
130
err = snd_ctl_get_power_state(ctl, &state);
137
void close_mixer_device(void)
143
set_view_mode(view_mode);
146
bool select_card_by_name(const char *device_name)
157
err = snd_mixer_attach(mixer, device_name);
161
msg = casprintf(_("Cannot open mixer device '%s'."), device_name);
162
show_alsa_error(msg, err);
167
mixer_device_name = cstrdup(device_name);
169
err = snd_mixer_load(mixer);
171
fatal_alsa_error(_("cannot load mixer controls"), err);
175
set_view_mode(view_mode);
179
static void show_help(void)
181
const char *help[] = {
184
_("F2 / System information"),
185
_("F3 Show playback controls"),
186
_("F4 Show capture controls"),
187
_("F5 Show all controls"),
188
_("Tab Toggle view mode (F3/F4/F5)"),
189
_("F6 S Select sound card"),
190
_("L Redraw screen"),
192
_("Left Move to the previous control"),
193
_("Right Move to the next control"),
195
_("Up/Down Change volume"),
196
_("+ - Change volume"),
197
_("Page Up/Dn Change volume in big steps"),
198
_("End Set volume to 0%"),
199
_("0-9 Set volume to 0%-90%"),
200
_("Q W E Increase left/both/right volumes"),
201
/* TRANSLATORS: or Y instead of Z */
202
_("Z X C Decrease left/both/right volumes"),
203
_("B Balance left and right volumes"),
206
/* TRANSLATORS: or , . */
207
_("< > Toggle left/right mute"),
209
_("Space Toggle capture"),
210
/* TRANSLATORS: or Insert Delete */
211
_("; ' Toggle left/right capture"),
214
_(" Tim Janik <timj@gtk.org>"),
215
_(" Jaroslav Kysela <perex@perex.cz>"),
216
_(" Clemens Ladisch <clemens@ladisch.de>"),
218
show_text(help, ARRAY_SIZE(help), _("Help"));
221
void refocus_control(void)
223
if (focus_control_index < controls_count) {
224
snd_mixer_selem_get_id(controls[focus_control_index].elem, current_selem_id);
225
current_control_flags = controls[focus_control_index].flags;
231
static struct control *get_focus_control(unsigned int type)
233
if (focus_control_index >= 0 &&
234
focus_control_index < controls_count &&
235
(controls[focus_control_index].flags & IS_ACTIVE) &&
236
(controls[focus_control_index].flags & type))
237
return &controls[focus_control_index];
242
static void change_enum_to_percent(struct control *control, int value)
246
unsigned int new_index;
250
i = ffs(control->enum_channel_bits) - 1;
251
err = snd_mixer_selem_get_enum_item(control->elem, i, &index);
257
} else if (value == 100) {
258
items = snd_mixer_selem_get_enum_items(control->elem);
261
new_index = items - 1;
263
if (new_index == index)
265
for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
266
if (control->enum_channel_bits & (1 << i))
267
snd_mixer_selem_set_enum_item(control->elem, i, new_index);
270
static void change_enum_relative(struct control *control, int delta)
278
items = snd_mixer_selem_get_enum_items(control->elem);
281
err = snd_mixer_selem_get_enum_item(control->elem, 0, &index);
284
new_index = (int)index + delta;
287
else if (new_index >= items)
288
new_index = items - 1;
289
if (new_index == index)
291
for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
292
if (control->enum_channel_bits & (1 << i))
293
snd_mixer_selem_set_enum_item(control->elem, i, new_index);
296
static void change_volume_to_percent(struct control *control, int value, unsigned int channels)
298
int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
299
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
303
if (!(control->flags & HAS_VOLUME_1))
305
if (control->flags & TYPE_PVOLUME) {
306
get_range_func = snd_mixer_selem_get_playback_volume_range;
307
set_func = snd_mixer_selem_set_playback_volume;
309
get_range_func = snd_mixer_selem_get_capture_volume_range;
310
set_func = snd_mixer_selem_set_capture_volume;
312
err = get_range_func(control->elem, &min, &max);
316
set_func(control->elem, control->volume_channels[0], min + (max - min) * value / 100);
317
if (channels & RIGHT)
318
set_func(control->elem, control->volume_channels[1], min + (max - min) * value / 100);
321
static void change_volume_relative(struct control *control, int delta, unsigned int channels)
323
int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
324
int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
325
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
331
if (!(control->flags & HAS_VOLUME_1))
333
if (control->flags & TYPE_PVOLUME) {
334
get_range_func = snd_mixer_selem_get_playback_volume_range;
335
get_func = snd_mixer_selem_get_playback_volume;
336
set_func = snd_mixer_selem_set_playback_volume;
338
get_range_func = snd_mixer_selem_get_capture_volume_range;
339
get_func = snd_mixer_selem_get_capture_volume;
340
set_func = snd_mixer_selem_set_capture_volume;
342
err = get_range_func(control->elem, &min, &max);
345
if (channels & LEFT) {
346
err = get_func(control->elem, control->volume_channels[0], &left);
350
if (channels & RIGHT) {
351
err = get_func(control->elem, control->volume_channels[1], &right);
355
if (channels & LEFT) {
356
value = left + delta;
359
else if (value > max)
362
set_func(control->elem, control->volume_channels[0], value);
364
if (channels & RIGHT) {
365
value = right + delta;
368
else if (value > max)
371
set_func(control->elem, control->volume_channels[1], value);
375
static void change_control_to_percent(int value, unsigned int channels)
377
struct control *control;
379
control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
382
if (control->flags & TYPE_ENUM)
383
change_enum_to_percent(control, value);
385
change_volume_to_percent(control, value, channels);
389
static void change_control_relative(int delta, unsigned int channels)
391
struct control *control;
393
control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
396
if (control->flags & TYPE_ENUM)
397
change_enum_relative(control, delta);
399
change_volume_relative(control, delta, channels);
403
static void toggle_switches(unsigned int type, unsigned int channels)
405
struct control *control;
406
unsigned int switch_1_mask;
407
int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *);
408
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int);
409
snd_mixer_selem_channel_id_t channel_ids[2];
413
control = get_focus_control(type);
416
if (type == TYPE_PSWITCH) {
417
switch_1_mask = HAS_PSWITCH_1;
418
get_func = snd_mixer_selem_get_playback_switch;
419
set_func = snd_mixer_selem_set_playback_switch;
420
channel_ids[0] = control->pswitch_channels[0];
421
channel_ids[1] = control->pswitch_channels[1];
423
switch_1_mask = HAS_CSWITCH_1;
424
get_func = snd_mixer_selem_get_capture_switch;
425
set_func = snd_mixer_selem_set_capture_switch;
426
channel_ids[0] = control->cswitch_channels[0];
427
channel_ids[1] = control->cswitch_channels[1];
429
if (!(control->flags & switch_1_mask))
431
if (channels & LEFT) {
432
err = get_func(control->elem, channel_ids[0], &left);
436
if (channels & RIGHT) {
437
err = get_func(control->elem, channel_ids[1], &right);
442
set_func(control->elem, channel_ids[0], !left);
443
if (channels & RIGHT)
444
set_func(control->elem, channel_ids[1], !right);
448
static void toggle_mute(unsigned int channels)
450
toggle_switches(TYPE_PSWITCH, channels);
453
static void toggle_capture(unsigned int channels)
455
toggle_switches(TYPE_CSWITCH, channels);
458
static void balance_volumes(void)
460
struct control *control;
464
control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME);
465
if (!control || !(control->flags & HAS_VOLUME_1))
467
if (control->flags & TYPE_PVOLUME) {
468
err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[0], &left);
471
err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[1], &right);
475
err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[0], &left);
478
err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[1], &right);
482
left = (left + right) / 2;
483
if (control->flags & TYPE_PVOLUME) {
484
snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[0], left);
485
snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[1], left);
487
snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[0], left);
488
snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[1], left);
493
static void on_handle_key(int key)
499
mixer_widget.close();
510
create_proc_files_list();
513
set_view_mode(VIEW_MODE_PLAYBACK);
516
set_view_mode(VIEW_MODE_CAPTURE);
519
set_view_mode(VIEW_MODE_ALL);
522
set_view_mode((enum view_mode)((view_mode + 1) % VIEW_MODE_COUNT));
527
create_card_select_list();
533
clearok(mixer_widget.window, TRUE);
539
if (focus_control_index > 0) {
540
--focus_control_index;
547
if (focus_control_index < controls_count - 1) {
548
++focus_control_index;
553
change_control_relative(5, LEFT | RIGHT);
556
change_control_relative(-5, LEFT | RIGHT);
561
change_control_to_percent(100, LEFT | RIGHT);
566
change_control_to_percent(0, LEFT | RIGHT);
574
change_control_relative(1, LEFT | RIGHT);
582
change_control_relative(-1, LEFT | RIGHT);
584
case '0': case '1': case '2': case '3': case '4':
585
case '5': case '6': case '7': case '8': case '9':
586
change_control_to_percent((key - '0') * 10, LEFT | RIGHT);
590
change_control_relative(1, LEFT);
596
change_control_relative(-1, LEFT);
600
change_control_relative(1, RIGHT);
604
change_control_relative(-1, RIGHT);
608
toggle_mute(LEFT | RIGHT);
624
toggle_capture(LEFT | RIGHT);
628
toggle_capture(LEFT);
632
toggle_capture(RIGHT);
637
static void create(void)
639
static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " ";
641
widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0,
642
attr_mixer_frame, WIDGET_BORDER);
643
if (screen_cols >= (sizeof(title) - 1) + 2) {
644
wattrset(mixer_widget.window, attr_mixer_active);
645
mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title);
649
set_view_mode(view_mode);
652
static void on_window_size_changed(void)
657
static void on_close(void)
659
widget_free(&mixer_widget);
662
void mixer_shutdown(void)
666
snd_mixer_close(mixer);
667
if (current_selem_id)
668
snd_mixer_selem_id_free(current_selem_id);
671
struct widget mixer_widget = {
672
.handle_key = on_handle_key,
673
.window_size_changed = on_window_size_changed,
677
void create_mixer_widget(void)