alsamixer: show channel names for multichannel controls

For multichannel mixer controls, add the channel name to each screen
control.

Also make some other small changes.

Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
This commit is contained in:
Clemens Ladisch 2009-05-25 10:26:22 +02:00
parent bde1d198c1
commit 5b6b5fd14b
38 changed files with 4628 additions and 2747 deletions

View file

@ -2,6 +2,20 @@ AM_CFLAGS = @CURSES_CFLAGS@ -DCURSESINC="@CURSESINC@"
LDADD = @CURSESLIB@ LDADD = @CURSESLIB@
bin_PROGRAMS = alsamixer bin_PROGRAMS = alsamixer
alsamixer_SOURCES = card_select.c card_select.h \
cli.c \
colors.c colors.h \
device_name.c device_name.h \
die.c die.h \
mainloop.c mainloop.h \
mem.c mem.h \
mixer_controls.c mixer_controls.h \
mixer_display.c mixer_display.h \
mixer_widget.c mixer_widget.h \
proc_files.c proc_files.h \
textbox.c textbox.h \
utils.c utils.h \
widget.c widget.h
man_MANS = alsamixer.1 man_MANS = alsamixer.1
EXTRA_DIST = alsamixer.1 EXTRA_DIST = alsamixer.1
alsamixer_INCLUDES = -I$(top_srcdir)/include alsamixer_INCLUDES = -I$(top_srcdir)/include

View file

@ -1,84 +0,0 @@
Using Alsamixer
===============
Alsamixer uses an ncurses interface, which may not display properly in
an xterm.
Start it by typing "alsamixer".
Optional flags:
alsamixer -h displays the available flags.
alsamixer -e starts in "exact" mode. See below...
alsamixer -c N selects the soundcard to control, where N is the number of
the card, counting from 1.
alsamixer -m selects which mixer device to control, counting from 0. This
is only applicable to soundcards that have more than one mixer to
control. It is the same as the amixer -d flag.
Keyboard commands:
==================
Left & right arrow keys are used to select the channel (or device,
depending on your preferred terminology). You can also use n (next)
and p (previous).
Up/down arrows control the volume for the currently selected device.
Both the left & right signals are controlled.
You can also use "+" or "-" to turn volumes up or down.
"M" toggles muting for the current channel (both left and right). You can
mute left and right independently by using , and . respectively.
SPACE toggles recording: the current channel will be added or removed from
the sources used for recording. This only works on valid input channels,
of course.
"L" re-draws the screen.
TAB does something interesting: it toggles the mode for volume display.
This affects the numbers you see, but not the operation of the level
controls. There seem to be two modes: one is percentages from 0-100, the
other is called "exact mode" and varies from channel to channel. This
shows you the settings as the soundcard understands them. All the channel
level ranges are from 0 to a power of 2 minus one (e.g. 0-31 or 0-63).
Quick Volume Changes
--------------------
PageUp increases volume by 10.
PageDown decreases volume by 10.
Home sets volume to 100.
End sets volume to 0.
You can also control left & right levels for the current channel
independently,
according to this chart:
Q | W | E <-- UP
-------------
Z | X | C <---DOWN
^ ^ ^
| | +-- Right
| |
| +--- Both
|
Left
If the current mixer channel is not a stereo channel, then all UP keys
will work like W, and all DOWN keys will work like X.
Exiting
=======
You can exit with ALT + Q, or by hitting ESC.
-----------------------------------------------------------------
Alsamixer has been written by Tim Janik <timj@gtk.org> and
been furtherly improved by Jaroslav Kysela <perex@perex.cz>.
This document was provided by Paul Winkler <zarmzarm@erols.com>.

View file

@ -1,4 +1,4 @@
.TH ALSAMIXER 1 "15 May 2001" .TH ALSAMIXER 1 "22 May 2009"
.SH NAME .SH NAME
alsamixer \- soundcard mixer for ALSA soundcard driver, with ncurses interface alsamixer \- soundcard mixer for ALSA soundcard driver, with ncurses interface
.SH SYNOPSIS .SH SYNOPSIS
@ -12,30 +12,26 @@ soundcard drivers. It supports multiple soundcards with multiple devices.
.SH OPTIONS .SH OPTIONS
.TP .TP
\fI\-h, \-help\fP \fI\-h, \-\-help\fP
Help: show available flags. Help: show available flags.
.TP .TP
\fI\-c\fP <card number or identification> \fI\-c, \-\-card\fP <card number or identification>
Select the soundcard to use, if you have more than one. Cards are Select the soundcard to use, if you have more than one. Cards are
numbered from 0 (the default). numbered from 0 (the default).
.TP .TP
\fI\-D\fP <device identification> \fI\-D, \-\-device\fP <device identification>
Select the mixer device to control. Select the mixer device to control.
.TP .TP
\fI\-g\fP \fI\-V, \-\-view\fP <mode>
Toggle the using of colors.
.TP
\fI\-s\fP
Minimize the mixer window.
.TP
\fI\-V\fP <view mode>
Select the starting view mode, either \fIplayback\fP, \fIcapture\fP or \fIall\fP. Select the starting view mode, either \fIplayback\fP, \fIcapture\fP or \fIall\fP.
.TP
\fI\-g, \-\-no\-color\fP
Toggle the using of colors.
.SH MIXER VIEWS .SH MIXER VIEWS
The top-left corner of \fBalsamixer\fP is the are to show some basic The top-left corner of \fBalsamixer\fP is the are to show some basic
@ -147,6 +143,13 @@ all UP keys will work like \fIW\fP, and all DOWN keys will work like \fIX\fP.
The number keys from \fI0\fP to \fI9\fP are to change the absolute volume The number keys from \fI0\fP to \fI9\fP are to change the absolute volume
quickly. They correspond to 0 to 90% volume. quickly. They correspond to 0 to 90% volume.
.SS
Selecting the Sound Card
You can select another sound card by pressing the \fIF6\fP or \fIS\fP keys.
This will show a list of available sound cards to choose from,
and an entry to enter the mixer device name by hand.
.SS .SS
Exiting Exiting
@ -169,6 +172,7 @@ fault. Plain old \fBxterm\fP seems to be fine.
.SH AUTHOR .SH AUTHOR
.B alsamixer .B alsamixer
has been written by Tim Janik <timj@gtk.org> and has been written by Tim Janik <timj@gtk.org> and
been further improved by Jaroslav Kysela <perex@perex.cz>. been further improved by Jaroslav Kysela <perex@perex.cz>
and Clemens Ladisch <clemens@ladisch.de>.
This manual page was provided by Paul Winkler <zarmzarm@erols.com>. This manual page was provided by Paul Winkler <zarmzarm@erols.com>.

File diff suppressed because it is too large Load diff

268
alsamixer/card_select.c Normal file
View file

@ -0,0 +1,268 @@
/*
* card_select.c - select a card by list or device name
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <alsa/asoundlib.h>
#include <menu.h>
#include "gettext_curses.h"
#include "die.h"
#include "mem.h"
#include "utils.h"
#include "colors.h"
#include "widget.h"
#include "mixer_widget.h"
#include "device_name.h"
#include "card_select.h"
struct card {
struct card *next;
char *indexstr;
char *name;
char *device_name;
};
static struct widget list_widget;
static struct card first_card;
static ITEM **items;
static MENU *menu;
static ITEM *initial_item;
static void on_key_enter(void)
{
ITEM *item = current_item(menu);
if (item) {
struct card *card = item_userptr(item);
if (card->device_name) {
if (select_card_by_name(card->device_name))
list_widget.close();
} else {
create_device_name_form();
}
}
}
static void on_menu_key(int key)
{
static const struct {
int key;
int request;
} key_map[] = {
{ KEY_DOWN, REQ_DOWN_ITEM },
{ KEY_UP, REQ_UP_ITEM },
{ KEY_HOME, REQ_FIRST_ITEM },
{ KEY_NPAGE, REQ_SCR_DPAGE },
{ KEY_PPAGE, REQ_SCR_UPAGE },
{ KEY_BEG, REQ_FIRST_ITEM },
{ KEY_END, REQ_LAST_ITEM },
};
unsigned int i;
for (i = 0; i < ARRAY_SIZE(key_map); ++i)
if (key_map[i].key == key) {
menu_driver(menu, key_map[i].request);
break;
}
}
static void on_handle_key(int key)
{
switch (key) {
case 27:
case KEY_CANCEL:
case 'q':
case 'Q':
list_widget.close();
break;
case 10:
case 13:
case KEY_ENTER:
on_key_enter();
break;
default:
on_menu_key(key);
break;
}
}
static bool create(void)
{
int rows, columns;
const char *title;
if (screen_lines < 3 || screen_cols < 10) {
beep();
list_widget.close();
return FALSE;
}
scale_menu(menu, &rows, &columns);
rows += 2;
columns += 2;
if (rows > screen_lines)
rows = screen_lines;
if (columns > screen_cols)
columns = screen_cols;
widget_init(&list_widget, rows, columns, SCREEN_CENTER, SCREEN_CENTER,
attr_menu, WIDGET_BORDER | WIDGET_SUBWINDOW);
title = _("Sound Card");
mvwprintw(list_widget.window, 0, (columns - 2 - get_mbs_width(title)) / 2, " %s ", title);
set_menu_win(menu, list_widget.window);
set_menu_sub(menu, list_widget.subwindow);
return TRUE;
}
static void on_window_size_changed(void)
{
unpost_menu(menu);
if (!create())
return;
post_menu(menu);
}
static void on_close(void)
{
unsigned int i;
struct card *card, *next_card;
unpost_menu(menu);
free_menu(menu);
for (i = 0; items[i]; ++i)
free_item(items[i]);
free(items);
for (card = first_card.next; card; card = next_card) {
next_card = card->next;
free(card->indexstr);
free(card->name);
free(card->device_name);
free(card);
}
widget_free(&list_widget);
}
void close_card_select_list(void)
{
on_close();
}
static struct widget list_widget = {
.handle_key = on_handle_key,
.window_size_changed = on_window_size_changed,
.close = on_close,
};
static int get_cards(void)
{
int count, number, err;
snd_ctl_t *ctl;
snd_ctl_card_info_t *info;
char buf[16];
struct card *card, *prev_card;
first_card.indexstr = "-";
first_card.name = _("(default)");
first_card.device_name = "default";
count = 1;
snd_ctl_card_info_alloca(&info);
prev_card = &first_card;
number = -1;
for (;;) {
err = snd_card_next(&number);
if (err < 0)
fatal_alsa_error(_("cannot enumerate sound cards"), err);
if (number < 0)
break;
sprintf(buf, "hw:%d", number);
err = snd_ctl_open(&ctl, buf, 0);
if (err < 0)
continue;
err = snd_ctl_card_info(ctl, info);
snd_ctl_close(ctl);
if (err < 0)
continue;
card = ccalloc(1, sizeof *card);
sprintf(buf, "%d", number);
card->indexstr = cstrdup(buf);
card->name = cstrdup(snd_ctl_card_info_get_name(info));
sprintf(buf, "hw:%d", number);
card->device_name = cstrdup(buf);
prev_card->next = card;
prev_card = card;
++count;
}
card = ccalloc(1, sizeof *card);
card->indexstr = cstrdup(" ");
card->name = cstrdup(_("enter device name..."));
prev_card->next = card;
++count;
return count;
}
static void create_list_items(int cards)
{
int i;
struct card *card;
ITEM *item;
initial_item = NULL;
items = ccalloc(cards + 1, sizeof(ITEM*));
i = 0;
for (card = &first_card; card; card = card->next) {
item = new_item(card->indexstr, card->name);
if (!item)
fatal_error("cannot create menu item");
set_item_userptr(item, card);
items[i++] = item;
if (!initial_item &&
mixer_device_name &&
(!card->device_name ||
!strcmp(card->device_name, mixer_device_name)))
initial_item = item;
}
assert(i == cards);
}
void create_card_select_list(void)
{
int cards;
cards = get_cards();
create_list_items(cards);
menu = new_menu(items);
if (!menu)
fatal_error("cannot create menu");
set_menu_fore(menu, attr_menu_selected);
set_menu_back(menu, attr_menu);
set_menu_mark(menu, NULL);
if (initial_item)
set_current_item(menu, initial_item);
set_menu_spacing(menu, 2, 1, 1);
menu_opts_on(menu, O_SHOWDESC);
if (!create())
return;
post_menu(menu);
}

7
alsamixer/card_select.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef CARD_SELECT_H_INCLUDED
#define CARD_SELECT_H_INCLUDED
void create_card_select_list(void);
void close_card_select_list(void);
#endif

135
alsamixer/cli.c Normal file
View file

@ -0,0 +1,135 @@
/*
* alsamixer - curses mixer for the ALSA project
* Copyright (c) 1998,1999 Tim Janik <timj@gtk.org>
* Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <getopt.h>
#include <alsa/asoundlib.h>
#include "gettext_curses.h"
#include "mixer_widget.h"
#include "mainloop.h"
static int use_color = 1;
static struct snd_mixer_selem_regopt selem_regopt = {
.ver = 1,
.abstract = SND_MIXER_SABSTRACT_NONE,
.device = "default",
};
static void show_help(void)
{
puts(_("Usage: alsamixer [options]"));
puts(_("Useful options:\n"
" -h, --help this help\n"
" -c, --card=NUMBER sound card number or id\n"
" -D, --device=NAME mixer device name\n"
" -V, --view=MODE starting view mode: playback/capture/all"));
puts(_("Debugging options:\n"
" -g, --no-color toggle using of colors\n"
" -a, --abstraction=NAME mixer abstraction level: none/basic"));
}
static void parse_options(int argc, char *argv[])
{
static const char short_options[] = "hc:D:V:gsa:";
static const struct option long_options[] = {
{ .name = "help", .val = 'h' },
{ .name = "card", .has_arg = 1, .val = 'c' },
{ .name = "device", .has_arg = 1, .val = 'D' },
{ .name = "view", .has_arg = 1, .val = 'V' },
{ .name = "no-color", .val = 'g' },
{ .name = "abstraction", .has_arg = 1, .val = 'a' },
{ }
};
int option;
int card_index;
static char name_buf[16];
while ((option = getopt_long(argc, argv, short_options,
long_options, NULL)) != -1) {
switch (option) {
case '?':
case 'h':
show_help();
exit(EXIT_SUCCESS);
case 'c':
card_index = snd_card_get_index(optarg);
if (card_index < 0) {
fprintf(stderr, _("invalid card index: %s\n"), optarg);
goto fail;
}
sprintf(name_buf, "hw:%d", card_index);
selem_regopt.device = name_buf;
break;
case 'D':
selem_regopt.device = optarg;
break;
case 'V':
if (*optarg == 'p' || *optarg == 'P')
view_mode = VIEW_MODE_PLAYBACK;
else if (*optarg == 'c' || *optarg == 'C')
view_mode = VIEW_MODE_CAPTURE;
else
view_mode = VIEW_MODE_ALL;
break;
case 'g':
use_color = !use_color;
break;
case 'a':
if (!strcmp(optarg, "none"))
selem_regopt.abstract = SND_MIXER_SABSTRACT_NONE;
else if (!strcmp(optarg, "basic"))
selem_regopt.abstract = SND_MIXER_SABSTRACT_BASIC;
else {
fprintf(stderr, _("unknown abstraction level: %s\n"), optarg);
goto fail;
}
break;
default:
fprintf(stderr, _("unknown option: %c\n"), option);
fail:
fputs(_("try `alsamixer --help' for more information\n"), stderr);
exit(EXIT_FAILURE);
}
}
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL, "");
#ifdef ENABLE_NLS_IN_CURSES
textdomain(PACKAGE);
#endif
parse_options(argc, argv);
create_mixer_object(&selem_regopt);
initialize_curses(use_color);
create_mixer_widget();
mainloop();
shutdown();
return 0;
}

103
alsamixer/colors.c Normal file
View file

@ -0,0 +1,103 @@
/*
* colors.c - color and attribute definitions
* Copyright (c) 1998,1999 Tim Janik <timj@gtk.org>
* Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include CURSESINC
#include "colors.h"
int attr_mixer_frame;
int attr_mixer_text;
int attr_mixer_active;
int attr_ctl_frame;
int attr_ctl_mute;
int attr_ctl_nomute;
int attr_ctl_capture;
int attr_ctl_nocapture;
int attr_ctl_label;
int attr_ctl_label_focus;
int attr_ctl_mark_focus;
int attr_ctl_bar;
int attr_ctl_inactive;
int attr_ctl_label_inactive;
int attr_errormsg;
int attr_infomsg;
int attr_textbox;
int attr_textfield;
int attr_menu;
int attr_menu_selected;
void init_colors(int use_color)
{
if (!!has_colors() == !!use_color) {
start_color();
init_pair(1, COLOR_CYAN, COLOR_BLACK);
init_pair(2, COLOR_YELLOW, COLOR_BLACK);
init_pair(3, COLOR_WHITE, COLOR_GREEN);
init_pair(4, COLOR_RED, COLOR_BLACK);
init_pair(5, COLOR_WHITE, COLOR_BLACK);
init_pair(6, COLOR_WHITE, COLOR_BLUE);
init_pair(7, COLOR_RED, COLOR_BLUE);
init_pair(8, COLOR_GREEN, COLOR_GREEN);
init_pair(9, COLOR_WHITE, COLOR_RED);
attr_mixer_frame = COLOR_PAIR(1);
attr_mixer_text = COLOR_PAIR(1);
attr_mixer_active = A_BOLD | COLOR_PAIR(2);
attr_ctl_frame = A_BOLD | COLOR_PAIR(1);
attr_ctl_mute = COLOR_PAIR(1);
attr_ctl_nomute = A_BOLD | COLOR_PAIR(3);
attr_ctl_capture = A_BOLD | COLOR_PAIR(4);
attr_ctl_nocapture = COLOR_PAIR(5);
attr_ctl_label = A_BOLD | COLOR_PAIR(6);
attr_ctl_label_focus = A_BOLD | COLOR_PAIR(7);
attr_ctl_mark_focus = A_BOLD | COLOR_PAIR(4);
attr_ctl_bar = A_BOLD | COLOR_PAIR(8);
attr_ctl_inactive = COLOR_PAIR(5);
attr_ctl_label_inactive = A_REVERSE | COLOR_PAIR(5);
attr_errormsg = A_BOLD | COLOR_PAIR(9);
attr_infomsg = A_BOLD | COLOR_PAIR(6);
attr_textbox = A_BOLD | COLOR_PAIR(6);
attr_textfield = A_REVERSE | COLOR_PAIR(5);
attr_menu = A_BOLD | COLOR_PAIR(6);
attr_menu_selected = A_REVERSE | COLOR_PAIR(6);
} else {
attr_mixer_frame = A_NORMAL;
attr_mixer_text = A_NORMAL;
attr_mixer_active = A_BOLD;
attr_ctl_frame = A_BOLD;
attr_ctl_mute = A_NORMAL;
attr_ctl_nomute = A_BOLD;
attr_ctl_capture = A_BOLD;
attr_ctl_nocapture = A_NORMAL;
attr_ctl_label = A_REVERSE;
attr_ctl_label_focus = A_REVERSE | A_BOLD;
attr_ctl_mark_focus = A_BOLD;
attr_ctl_bar = A_BOLD;
attr_ctl_inactive = A_NORMAL;
attr_ctl_label_inactive = A_REVERSE;
attr_errormsg = A_STANDOUT;
attr_infomsg = A_NORMAL;
attr_textbox = A_NORMAL;
attr_textfield = A_REVERSE;
attr_menu = A_NORMAL;
attr_menu_selected = A_REVERSE;
}
}

27
alsamixer/colors.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef COLORS_H_INCLUDED
#define COLORS_H_INCLUDED
extern int attr_mixer_frame;
extern int attr_mixer_text;
extern int attr_mixer_active;
extern int attr_ctl_frame;
extern int attr_ctl_mute;
extern int attr_ctl_nomute;
extern int attr_ctl_capture;
extern int attr_ctl_nocapture;
extern int attr_ctl_label;
extern int attr_ctl_label_focus;
extern int attr_ctl_mark_focus;
extern int attr_ctl_bar;
extern int attr_ctl_inactive;
extern int attr_ctl_label_inactive;
extern int attr_errormsg;
extern int attr_infomsg;
extern int attr_textbox;
extern int attr_textfield;
extern int attr_menu;
extern int attr_menu_selected;
void init_colors(int use_color);
#endif

197
alsamixer/device_name.c Normal file
View file

@ -0,0 +1,197 @@
/*
* device_name_form.c - ask for sound control device name
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdlib.h>
#include <string.h>
#include CURSESINC
#include <form.h>
#include "gettext_curses.h"
#include "die.h"
#include "mem.h"
#include "utils.h"
#include "colors.h"
#include "widget.h"
#include "mixer_widget.h"
#include "card_select.h"
#include "device_name.h"
static struct widget form_widget;
static FIELD *fields[3];
static FORM *form;
static char *dup_current_name(void)
{
int rows, cols, max, i;
char *s;
if (form_driver(form, REQ_VALIDATION) == E_OK) {
dynamic_field_info(fields[1], &rows, &cols, &max);
s = ccalloc(1, cols + 1);
memcpy(s, field_buffer(fields[1], 0), cols);
for (i = strlen(s) - 1; i >= 0 && s[i] == ' '; --i)
s[i] = '\0';
return s;
} else {
return cstrdup("");
}
}
static void on_key_enter(void)
{
char *s;
bool ok;
s = dup_current_name();
ok = select_card_by_name(s);
free(s);
if (ok) {
form_widget.close();
close_card_select_list();
}
}
static void on_form_key(int key)
{
static const struct {
int key;
int request;
} key_map[] = {
{ KEY_LEFT, REQ_PREV_CHAR },
{ KEY_RIGHT, REQ_NEXT_CHAR },
{ KEY_HOME, REQ_BEG_FIELD },
{ KEY_BACKSPACE, REQ_DEL_PREV },
{ KEY_DC, REQ_DEL_CHAR },
{ KEY_BEG, REQ_BEG_FIELD },
{ KEY_END, REQ_END_FIELD },
};
unsigned int i;
if (key >= 32 && key < 256) {
form_driver(form, key);
return;
}
for (i = 0; i < ARRAY_SIZE(key_map); ++i)
if (key_map[i].key == key) {
form_driver(form, key_map[i].request);
break;
}
}
static void on_handle_key(int key)
{
switch (key) {
case 27:
case KEY_CANCEL:
form_widget.close();
break;
case 10:
case 13:
case KEY_ENTER:
on_key_enter();
break;
default:
on_form_key(key);
break;
}
}
static bool create(void)
{
const char *title;
if (screen_lines < 6 || screen_cols < 36) {
form_widget.close();
beep();
return FALSE;
}
widget_init(&form_widget,
6, 36, SCREEN_CENTER, SCREEN_CENTER,
attr_textbox, WIDGET_BORDER | WIDGET_SUBWINDOW | WIDGET_CURSOR_VISIBLE);
title = _("Sound Card");
mvwprintw(form_widget.window, 0, (36 - 2 - get_mbs_width(title)) / 2, " %s ", title);
set_form_win(form, form_widget.window);
set_form_sub(form, form_widget.subwindow);
return TRUE;
}
static void on_window_size_changed(void)
{
form_driver(form, REQ_VALIDATION); /* save field value */
unpost_form(form);
if (!create())
return;
/*
* This call fails because ncurses does not allow changing options of
* the current field, and we cannot change the current field because
* there is only one. The only way to make this work would be to throw
* away and recreate all fields.
*/
field_opts_off(fields[1], O_BLANK);
post_form(form);
}
static void on_close(void)
{
unpost_form(form);
free_form(form);
free_field(fields[0]);
free_field(fields[1]);
widget_free(&form_widget);
}
static struct widget form_widget = {
.handle_key = on_handle_key,
.window_size_changed = on_window_size_changed,
.close = on_close,
};
void create_device_name_form(void)
{
fields[0] = new_field(1, 32, 1, 1, 0, 0);
if (!fields[0])
fatal_error("cannot create field");
field_opts_off(fields[0], O_ACTIVE);
field_opts_off(fields[0], O_EDIT);
set_field_fore(fields[0], attr_textbox);
set_field_back(fields[0], attr_textbox);
set_field_buffer(fields[0], 0, _("Device name:"));
fields[1] = new_field(1, 32, 2, 1, 0, 0);
if (!fields[1])
fatal_error("cannot create field");
field_opts_off(fields[1], O_AUTOSKIP);
field_opts_off(fields[1], O_NULLOK);
field_opts_off(fields[1], O_STATIC);
set_field_fore(fields[1], attr_textfield);
set_field_back(fields[1], attr_textfield);
set_field_buffer(fields[1], 0, mixer_device_name);
form = new_form(fields);
if (!form)
fatal_error("cannot create form");
if (!create())
return;
post_form(form);
}

6
alsamixer/device_name.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef DEVICE_NAME_FORM_H_INCLUDED
#define DEVICE_NAME_FORM_H_INCLUDED
void create_device_name_form(void);
#endif

39
alsamixer/die.c Normal file
View file

@ -0,0 +1,39 @@
/*
* die.c - error handlers
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include "gettext_curses.h"
#include "mainloop.h"
#include "die.h"
void fatal_error(const char *msg)
{
shutdown();
fprintf(stderr, "%s\n", msg);
exit(EXIT_FAILURE);
}
void fatal_alsa_error(const char *msg, int err)
{
shutdown();
fprintf(stderr, _("%s: %s\n"), msg, snd_strerror(err));
exit(EXIT_FAILURE);
}

7
alsamixer/die.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef DIE_H_INCLUDED
#define DIE_H_INCLUDED
void fatal_error(const char *msg) __attribute__((__noreturn__));
void fatal_alsa_error(const char *msg, int err) __attribute__((__noreturn__));
#endif

135
alsamixer/mainloop.c Normal file
View file

@ -0,0 +1,135 @@
/*
* mainloop.c - main loop
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <poll.h>
#include <panel.h>
#include <alsa/asoundlib.h>
#include "mem.h"
#include "die.h"
#include "colors.h"
#include "widget.h"
#include "mixer_widget.h"
#include "mixer_display.h"
#include "mainloop.h"
static WINDOW *curses_initialized;
static void black_hole_error_handler(const char *file, int line,
const char *function, int err,
const char *fmt, ...)
{
}
void initialize_curses(bool use_color)
{
curses_initialized = initscr();
cbreak();
noecho();
#ifdef NCURSES_VERSION
set_escdelay(100);
#endif
window_size_changed(); /* update screen_lines/cols */
init_colors(use_color);
snd_lib_error_set_handler(black_hole_error_handler);
}
void shutdown(void)
{
if (curses_initialized) {
clear();
refresh();
curs_set(1);
endwin();
}
mixer_shutdown();
}
void mainloop(void)
{
struct pollfd *pollfds = NULL;
int nfds = 0, n;
struct widget *active_widget;
unsigned short revents;
int key;
int err;
for (;;) {
update_panels();
doupdate();
active_widget = get_active_widget();
if (!active_widget)
break;
n = 1 + snd_mixer_poll_descriptors_count(mixer);
if (n != nfds) {
free(pollfds);
nfds = n;
pollfds = ccalloc(nfds, sizeof *pollfds);
pollfds[0].fd = fileno(stdin);
pollfds[0].events = POLLIN;
}
err = snd_mixer_poll_descriptors(mixer, &pollfds[1], nfds - 1);
if (err < 0)
fatal_alsa_error("cannot get poll descriptors", err);
n = poll(pollfds, nfds, -1);
if (n < 0) {
if (errno == EINTR) {
pollfds[0].revents = 0;
doupdate(); /* handle SIGWINCH */
} else {
fatal_error("poll error");
}
}
if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
break;
if (pollfds[0].revents & POLLIN)
--n;
if (n > 0) {
err = snd_mixer_poll_descriptors_revents(mixer, &pollfds[1], nfds - 1, &revents);
if (err < 0)
fatal_alsa_error("cannot get poll events", err);
if (revents & (POLLERR | POLLNVAL))
close_mixer_device();
else if (revents & POLLIN)
snd_mixer_handle_events(mixer);
}
key = wgetch(active_widget->window);
while (key != ERR) {
#ifdef KEY_RESIZE
if (key == KEY_RESIZE)
window_size_changed();
else
#endif
active_widget->handle_key(key);
active_widget = get_active_widget();
if (!active_widget)
break;
key = wgetch(active_widget->window);
}
if (!active_widget)
break;
if (controls_changed)
display_controls();
}
free(pollfds);
}

10
alsamixer/mainloop.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef MAINLOOP_H_INCLUDED
#define MAINLOOP_H_INCLUDED
#include CURSESINC
void initialize_curses(bool use_color);
void mainloop(void);
void shutdown(void);
#endif

68
alsamixer/mem.c Normal file
View file

@ -0,0 +1,68 @@
/*
* mem.c - memory allocation checkers
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include "die.h"
#include "mem.h"
static void check(void *p)
{
if (!p)
fatal_error("out of memory");
}
void *ccalloc(size_t n, size_t size)
{
void *mem = calloc(n, size);
if (n && size)
check(mem);
return mem;
}
void *crealloc(void *ptr, size_t new_size)
{
ptr = realloc(ptr, new_size);
if (new_size)
check(ptr);
return ptr;
}
char *cstrdup(const char *s)
{
char *str = strdup(s);
check(str);
return str;
}
char *casprintf(const char *fmt, ...)
{
va_list ap;
char *str;
va_start(ap, fmt);
if (vasprintf(&str, fmt, ap) < 0)
check(NULL);
va_end(ap);
return str;
}

11
alsamixer/mem.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef MEM_H_INCLUDED
#define MEM_H_INCLUDED
#include <stddef.h>
void *ccalloc(size_t n, size_t size);
void *crealloc(void *ptr, size_t new_size);
char *cstrdup(const char *s);
char *casprintf(const char *fmt, ...) __attribute__((__format__(printf, 1, 2)));
#endif

521
alsamixer/mixer_controls.c Normal file
View file

@ -0,0 +1,521 @@
/*
* mixer_controls.c - handles mixer controls and mapping from selems
* Copyright (c) 1998,1999 Tim Janik <timj@gtk.org>
* Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include CURSESINC
#include <alsa/asoundlib.h>
#include "utils.h"
#include "mem.h"
#include "mixer_display.h"
#include "mixer_widget.h"
#include "mixer_controls.h"
struct control *controls;
unsigned int controls_count;
static const snd_mixer_selem_channel_id_t supported_channels[] = {
SND_MIXER_SCHN_FRONT_LEFT,
SND_MIXER_SCHN_FRONT_RIGHT,
SND_MIXER_SCHN_REAR_LEFT,
SND_MIXER_SCHN_REAR_RIGHT,
SND_MIXER_SCHN_FRONT_CENTER,
SND_MIXER_SCHN_WOOFER,
SND_MIXER_SCHN_SIDE_LEFT,
SND_MIXER_SCHN_SIDE_RIGHT,
};
#define LAST_SUPPORTED_CHANNEL SND_MIXER_SCHN_SIDE_RIGHT
static const snd_mixer_selem_channel_id_t control_channels[][2] = {
{ SND_MIXER_SCHN_FRONT_LEFT, SND_MIXER_SCHN_FRONT_RIGHT },
{ SND_MIXER_SCHN_REAR_LEFT, SND_MIXER_SCHN_REAR_RIGHT },
{ SND_MIXER_SCHN_FRONT_CENTER, SND_MIXER_SCHN_UNKNOWN },
{ SND_MIXER_SCHN_WOOFER, SND_MIXER_SCHN_UNKNOWN },
{ SND_MIXER_SCHN_SIDE_LEFT, SND_MIXER_SCHN_SIDE_RIGHT },
};
bool are_there_any_controls(void)
{
snd_mixer_elem_t *elem;
unsigned int i;
for (elem = snd_mixer_first_elem(mixer);
elem;
elem = snd_mixer_elem_next(elem)) {
if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
continue;
if (snd_mixer_selem_is_enumerated(elem))
return TRUE;
if (snd_mixer_selem_has_playback_volume_joined(elem) ||
snd_mixer_selem_has_capture_volume_joined(elem) ||
snd_mixer_selem_has_playback_switch_joined(elem) ||
snd_mixer_selem_has_capture_switch_joined(elem))
return TRUE;
for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
if (snd_mixer_selem_has_playback_channel(elem, supported_channels[i]) ||
snd_mixer_selem_has_capture_channel(elem, supported_channels[i]))
return TRUE;
}
return FALSE;
}
static bool has_more_than_front_capture_channels(snd_mixer_elem_t *elem)
{
unsigned int i;
for (i = 2; i < ARRAY_SIZE(supported_channels); ++i)
if (snd_mixer_selem_has_capture_channel(elem, supported_channels[i]))
return TRUE;
return FALSE;
}
static bool has_any_control_channel(snd_mixer_elem_t *elem,
const snd_mixer_selem_channel_id_t channels[2],
int (*has_channel)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t))
{
return has_channel(elem, channels[0]) ||
(channels[1] != SND_MIXER_SCHN_UNKNOWN && has_channel(elem, channels[1]));
}
static bool has_merged_cswitch(snd_mixer_elem_t *elem)
{
bool pvol, psw;
unsigned int i;
pvol = snd_mixer_selem_has_playback_volume(elem);
psw = snd_mixer_selem_has_playback_switch(elem);
if ((pvol || psw) &&
snd_mixer_selem_has_capture_switch(elem) &&
!snd_mixer_selem_has_capture_volume(elem)) {
if (snd_mixer_selem_has_capture_switch_joined(elem))
return TRUE;
else if (((pvol && snd_mixer_selem_has_playback_volume_joined(elem)) ||
(psw && snd_mixer_selem_has_playback_switch_joined(elem))) &&
has_more_than_front_capture_channels(elem))
return FALSE;
for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
if (has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_capture_channel) &&
!has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_playback_channel))
return FALSE;
}
return TRUE;
}
return FALSE;
}
static unsigned int get_playback_controls_count(snd_mixer_elem_t *elem)
{
unsigned int count = 0;
unsigned int i;
int has_vol, has_sw;
has_vol = snd_mixer_selem_has_playback_volume(elem);
has_sw = snd_mixer_selem_has_playback_switch(elem);
if (!has_vol && !has_sw)
return 0;
if ((!has_vol || snd_mixer_selem_has_playback_volume_joined(elem)) &&
(!has_sw || snd_mixer_selem_has_playback_switch_joined(elem)))
return 1;
for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
if (snd_mixer_selem_has_playback_channel(elem, control_channels[i][0]) ||
(control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
snd_mixer_selem_has_playback_channel(elem, control_channels[i][1])))
++count;
}
return count;
}
static unsigned int get_capture_controls_count(snd_mixer_elem_t *elem)
{
unsigned int count = 0;
unsigned int i;
int has_vol, has_sw;
has_vol = snd_mixer_selem_has_capture_volume(elem);
has_sw = snd_mixer_selem_has_capture_switch(elem);
if ((!has_vol && !has_sw) ||
(view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem)))
return 0;
if ((!has_vol || snd_mixer_selem_has_capture_volume_joined(elem)) &&
(!has_sw || snd_mixer_selem_has_capture_switch_joined(elem)))
return 1;
for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0]) ||
(control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
snd_mixer_selem_has_capture_channel(elem, control_channels[i][1])))
++count;
}
return count;
}
static unsigned int get_controls_count_for_elem(snd_mixer_elem_t *elem)
{
unsigned int p, c;
if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
return 0;
if (snd_mixer_selem_is_enumerated(elem)) {
switch (view_mode) {
case VIEW_MODE_PLAYBACK:
return snd_mixer_selem_is_enum_capture(elem) ? 0 : 1;
case VIEW_MODE_CAPTURE:
return snd_mixer_selem_is_enum_capture(elem) ? 1 : 0;
case VIEW_MODE_ALL:
default:
return 1;
}
}
switch (view_mode) {
case VIEW_MODE_PLAYBACK:
return get_playback_controls_count(elem);
case VIEW_MODE_CAPTURE:
return get_capture_controls_count(elem);
case VIEW_MODE_ALL:
default:
p = get_playback_controls_count(elem);
c = get_capture_controls_count(elem);
return has_merged_cswitch(elem) ? p : p + c;
}
}
static void create_name(struct control *control)
{
unsigned int index;
char *s;
index = snd_mixer_selem_get_index(control->elem);
if (index > 0)
control->name = casprintf("%s %u", snd_mixer_selem_get_name(control->elem), index);
else
control->name = cstrdup(snd_mixer_selem_get_name(control->elem));
while ((s = strstr(control->name, "IEC958")) != NULL)
memcpy(s, "S/PDIF", 6);
}
static unsigned int create_controls_for_elem(snd_mixer_elem_t *elem, struct control *control)
{
unsigned int count = 0;
unsigned int i;
unsigned int multich_flag;
unsigned int enum_index;
struct control *front_control = NULL;
bool has_pvol, has_psw;
bool has_cvol, has_csw;
bool has_channel[LAST_SUPPORTED_CHANNEL + 1];
bool merged_cswitch;
bool has_ch0, has_ch1;
if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
return 0;
if (snd_mixer_selem_is_enumerated(elem)) {
if ((view_mode == VIEW_MODE_PLAYBACK && snd_mixer_selem_is_enum_capture(elem)) ||
(view_mode == VIEW_MODE_CAPTURE && !snd_mixer_selem_is_enum_capture(elem)))
return 0;
control->elem = elem;
control->flags = TYPE_ENUM;
control->enum_channel_bits = 0;
for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
if (snd_mixer_selem_get_enum_item(control->elem, (snd_mixer_selem_channel_id_t)i, &enum_index) >= 0)
control->enum_channel_bits |= 1 << i;
if (snd_mixer_selem_is_active(control->elem))
control->flags |= IS_ACTIVE;
create_name(control);
return 1;
}
has_pvol = snd_mixer_selem_has_playback_volume(elem);
has_psw = snd_mixer_selem_has_playback_switch(elem);
has_cvol = snd_mixer_selem_has_capture_volume(elem);
has_csw = snd_mixer_selem_has_capture_switch(elem);
merged_cswitch = view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem);
if (view_mode != VIEW_MODE_CAPTURE && (has_pvol || has_psw)) {
if ((!has_pvol || snd_mixer_selem_has_playback_volume_joined(elem)) &&
(!has_psw || snd_mixer_selem_has_playback_switch_joined(elem))) {
control->elem = elem;
if (has_pvol) {
control->flags |= TYPE_PVOLUME | HAS_VOLUME_0;
control->volume_channels[0] = 0;
}
if (has_psw) {
control->flags |= TYPE_PSWITCH | HAS_PSWITCH_0;
control->pswitch_channels[0] = 0;
}
if (merged_cswitch) {
control->flags |= TYPE_CSWITCH;
if (snd_mixer_selem_has_capture_switch_joined(elem)) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = 0;
} else {
if (snd_mixer_selem_has_capture_channel(elem, control_channels[0][0])) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = control_channels[0][0];
}
if (control_channels[0][1] != SND_MIXER_SCHN_UNKNOWN &&
snd_mixer_selem_has_capture_channel(elem, control_channels[0][1])) {
control->flags |= HAS_CSWITCH_1;
control->cswitch_channels[1] = control_channels[0][1];
}
}
if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
control->cswitch_channels[0] = control->cswitch_channels[1];
}
}
if (snd_mixer_selem_is_active(control->elem))
control->flags |= IS_ACTIVE;
create_name(control);
++control;
++count;
} else {
multich_flag = 0;
for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
has_channel[supported_channels[i]] =
snd_mixer_selem_has_playback_channel(elem, supported_channels[i]);
for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
has_ch0 = has_channel[control_channels[i][0]];
has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
has_channel[control_channels[i][1]];
if (!has_ch0 && !has_ch1)
continue;
control->elem = elem;
if (has_pvol) {
control->flags |= TYPE_PVOLUME;
if (snd_mixer_selem_has_playback_volume_joined(elem)) {
control->flags |= HAS_VOLUME_0;
control->volume_channels[0] = 0;
} else {
if (has_ch0) {
control->flags |= HAS_VOLUME_0;
control->volume_channels[0] = control_channels[i][0];
}
if (has_ch1) {
control->flags |= HAS_VOLUME_1;
control->volume_channels[1] = control_channels[i][1];
}
}
}
if (has_psw) {
control->flags |= TYPE_PSWITCH;
if (snd_mixer_selem_has_playback_switch_joined(elem)) {
control->flags |= HAS_PSWITCH_0;
control->pswitch_channels[0] = 0;
} else {
if (has_ch0) {
control->flags |= HAS_PSWITCH_0;
control->pswitch_channels[0] = control_channels[i][0];
}
if (has_ch1) {
control->flags |= HAS_PSWITCH_1;
control->pswitch_channels[1] = control_channels[i][1];
}
}
}
if (merged_cswitch) {
control->flags |= TYPE_CSWITCH;
if (snd_mixer_selem_has_capture_switch_joined(elem)) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = 0;
} else {
if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0])) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = control_channels[i][0];
}
if (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
snd_mixer_selem_has_capture_channel(elem, control_channels[i][1])) {
control->flags |= HAS_CSWITCH_1;
control->cswitch_channels[1] = control_channels[i][1];
}
}
}
if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) {
control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1;
control->volume_channels[0] = control->volume_channels[1];
}
if ((control->flags & (HAS_PSWITCH_0 | HAS_PSWITCH_1)) == HAS_PSWITCH_1) {
control->flags ^= HAS_PSWITCH_0 | HAS_PSWITCH_1;
control->pswitch_channels[0] = control->pswitch_channels[1];
}
if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
control->cswitch_channels[0] = control->cswitch_channels[1];
}
if (snd_mixer_selem_is_active(control->elem))
control->flags |= IS_ACTIVE;
create_name(control);
if (i == 0)
front_control = control;
else {
front_control->flags |= IS_MULTICH | 0;
control->flags |= IS_MULTICH | i;
}
++control;
++count;
}
}
}
if (view_mode != VIEW_MODE_PLAYBACK && (has_cvol || has_csw) && !merged_cswitch) {
if ((!has_cvol || snd_mixer_selem_has_capture_volume_joined(elem)) &&
(!has_csw || snd_mixer_selem_has_capture_switch_joined(elem))) {
control->elem = elem;
if (has_cvol) {
control->flags |= TYPE_CVOLUME | HAS_VOLUME_0;
control->volume_channels[0] = 0;
}
if (has_csw) {
control->flags |= TYPE_CSWITCH | HAS_CSWITCH_0;
control->cswitch_channels[0] = 0;
}
if (snd_mixer_selem_is_active(control->elem))
control->flags |= IS_ACTIVE;
create_name(control);
++control;
++count;
} else {
for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
has_channel[supported_channels[i]] =
snd_mixer_selem_has_capture_channel(elem, supported_channels[i]);
for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
has_ch0 = has_channel[control_channels[i][0]];
has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
has_channel[control_channels[i][1]];
if (!has_ch0 && !has_ch1)
continue;
control->elem = elem;
if (has_cvol) {
control->flags |= TYPE_CVOLUME;
if (snd_mixer_selem_has_capture_volume_joined(elem)) {
control->flags |= HAS_VOLUME_0;
control->volume_channels[0] = 0;
} else {
if (has_ch0) {
control->flags |= HAS_VOLUME_0;
control->volume_channels[0] = control_channels[i][0];
}
if (has_ch1) {
control->flags |= HAS_VOLUME_1;
control->volume_channels[1] = control_channels[i][1];
}
}
}
if (has_csw) {
control->flags |= TYPE_CSWITCH;
if (snd_mixer_selem_has_capture_switch_joined(elem)) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = 0;
} else {
if (has_ch0) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = control_channels[i][0];
}
if (has_ch1) {
control->flags |= HAS_CSWITCH_1;
control->cswitch_channels[1] = control_channels[i][1];
}
}
}
if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) {
control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1;
control->volume_channels[0] = control->volume_channels[1];
}
if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
control->cswitch_channels[0] = control->cswitch_channels[1];
}
if (snd_mixer_selem_is_active(control->elem))
control->flags |= IS_ACTIVE;
create_name(control);
if (i == 0)
front_control = control;
else {
front_control->flags |= IS_MULTICH | 0;
control->flags |= IS_MULTICH | i;
}
++control;
++count;
}
}
}
return count;
}
static void search_for_focus_control(void)
{
snd_mixer_elem_t *elem;
unsigned int i;
elem = snd_mixer_find_selem(mixer, current_selem_id);
if (elem)
for (i = 0; i < controls_count; ++i)
if (controls[i].elem == elem) {
focus_control_index = i;
for (;;) {
++i;
if (i >= controls_count || controls[i].elem != elem)
return;
if (controls[i].flags == current_control_flags) {
focus_control_index = i;
return;
}
}
}
focus_control_index = 0;
}
void free_controls(void)
{
unsigned int i;
for (i = 0; i < controls_count; ++i)
free(controls[i].name);
free(controls);
controls = NULL;
controls_count = 0;
}
void create_controls(void)
{
snd_mixer_elem_t *elem;
struct control *control;
free_controls();
for (elem = snd_mixer_first_elem(mixer);
elem;
elem = snd_mixer_elem_next(elem))
controls_count += get_controls_count_for_elem(elem);
if (controls_count > 0) {
controls = ccalloc(controls_count, sizeof *controls);
control = controls;
for (elem = snd_mixer_first_elem(mixer);
elem;
elem = snd_mixer_elem_next(elem))
control += create_controls_for_elem(elem, control);
assert(control == controls + controls_count);
}
compute_controls_layout();
display_view_mode();
search_for_focus_control();
refocus_control();
}

View file

@ -0,0 +1,37 @@
#ifndef MIXER_CONTROLS_H_INCLUDED
#define MIXER_CONTROLS_H_INCLUDED
#include <alsa/asoundlib.h>
struct control {
snd_mixer_elem_t *elem;
char *name;
unsigned int flags;
#define TYPE_PVOLUME (1u << 4)
#define TYPE_CVOLUME (1u << 5)
#define TYPE_PSWITCH (1u << 6)
#define TYPE_CSWITCH (1u << 7)
#define TYPE_ENUM (1u << 8)
#define HAS_VOLUME_0 (1u << 9)
#define HAS_VOLUME_1 (1u << 10)
#define HAS_PSWITCH_0 (1u << 11)
#define HAS_PSWITCH_1 (1u << 12)
#define HAS_CSWITCH_0 (1u << 13)
#define HAS_CSWITCH_1 (1u << 14)
#define IS_MULTICH (1u << 15)
#define IS_ACTIVE (1u << 16)
#define MULTICH_MASK (0x0000f)
snd_mixer_selem_channel_id_t volume_channels[2];
snd_mixer_selem_channel_id_t pswitch_channels[2];
snd_mixer_selem_channel_id_t cswitch_channels[2];
unsigned int enum_channel_bits;
};
extern struct control *controls;
extern unsigned int controls_count;
bool are_there_any_controls(void);
void create_controls(void);
void free_controls(void);
#endif

739
alsamixer/mixer_display.c Normal file
View file

@ -0,0 +1,739 @@
/*
* mixer_display.c - handles displaying of mixer widget and controls
* Copyright (c) 1874 Lewis Carroll
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include CURSESINC
#include <alsa/asoundlib.h>
#include "gettext_curses.h"
#include "utils.h"
#include "mem.h"
#include "colors.h"
#include "widget.h"
#include "mixer_widget.h"
#include "mixer_controls.h"
#include "mixer_display.h"
enum align {
ALIGN_LEFT,
ALIGN_RIGHT,
ALIGN_CENTER,
};
static bool screen_too_small;
static bool has_info_items;
static int info_items_left;
static int info_items_width;
static int visible_controls;
static int first_visible_control_index;
static int first_control_x;
static int control_width;
static int control_name_width;
static int base_y;
static int volume_height;
static int cswitch_y;
static int values_y;
static int name_y;
static int channel_name_y;
static void display_string_in_field(int y, int x, const char *s, int width, enum align align)
{
int string_width;
const char *s_end;
int spaces;
int cur_y, cur_x;
wmove(mixer_widget.window, y, x);
string_width = width;
s_end = mbs_at_width(s, &string_width, -1);
if (string_width >= width) {
waddnstr(mixer_widget.window, s, s_end - s);
} else {
if (align != ALIGN_LEFT) {
spaces = width - string_width;
if (align == ALIGN_CENTER)
spaces /= 2;
if (spaces > 0)
wprintw(mixer_widget.window, "%*s", spaces, "");
}
waddstr(mixer_widget.window, s);
if (align != ALIGN_RIGHT) {
getyx(mixer_widget.window, cur_y, cur_x);
if (cur_y == y) {
spaces = x + width - cur_x;
if (spaces > 0)
wprintw(mixer_widget.window, "%*s", spaces, "");
}
}
}
}
void init_mixer_layout(void)
{
const char *labels_left[4] = {
_("Card:"),
_("Chip:"),
_("View:"),
_("Item:"),
};
const char *labels_right[4] = {
_("F1: Help"),
_("F2: System information"),
_("F6: Select sound card"),
_("Esc: Exit"),
};
unsigned int label_width_left, label_width_right;
unsigned int right_x, i;
screen_too_small = screen_lines < 14 || screen_cols < 12;
has_info_items = screen_lines >= 6;
if (!has_info_items)
return;
label_width_left = get_max_mbs_width(labels_left, 4);
label_width_right = get_max_mbs_width(labels_right, 4);
if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols)
label_width_right = 0;
if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols)
label_width_left = 0;
info_items_left = label_width_left ? 3 + label_width_left : 2;
right_x = screen_cols - label_width_right - 2;
info_items_width = right_x - info_items_left;
if (info_items_width < 1) {
has_info_items = FALSE;
return;
}
wattrset(mixer_widget.window, attr_mixer_text);
if (label_width_left)
for (i = 0; i < 4; ++i)
display_string_in_field(1 + i, 2, labels_left[i],
label_width_left, ALIGN_RIGHT);
if (label_width_right)
for (i = 0; i < 4; ++i)
display_string_in_field(1 + i, right_x, labels_right[i],
label_width_right, ALIGN_LEFT);
}
void display_card_info(void)
{
snd_hctl_t *hctl;
snd_ctl_t *ctl;
snd_ctl_card_info_t *card_info;
const char *card_name = NULL;
const char *mixer_name = NULL;
int err;
if (!has_info_items)
return;
snd_ctl_card_info_alloca(&card_info);
if (mixer_device_name)
err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
else
err = -1;
if (err >= 0) {
ctl = snd_hctl_ctl(hctl);
err = snd_ctl_card_info(ctl, card_info);
if (err >= 0) {
card_name = snd_ctl_card_info_get_name(card_info);
mixer_name = snd_ctl_card_info_get_mixername(card_info);
}
}
if (card_name)
wattrset(mixer_widget.window, attr_mixer_active);
else {
wattrset(mixer_widget.window, attr_mixer_text);
if (unplugged)
card_name = _("(unplugged)");
else
card_name = "-";
}
display_string_in_field(1, info_items_left, card_name, info_items_width, ALIGN_LEFT);
if (mixer_name)
wattrset(mixer_widget.window, attr_mixer_active);
else {
wattrset(mixer_widget.window, attr_mixer_text);
mixer_name = "-";
}
display_string_in_field(2, info_items_left, mixer_name, info_items_width, ALIGN_LEFT);
}
void display_view_mode(void)
{
const char *modes[3] = {
_("Playback"),
_("Capture"),
_("All"),
};
unsigned int widths[3];
bool has_view_mode;
int i;
if (!has_info_items)
return;
has_view_mode = controls_count > 0 || are_there_any_controls();
for (i = 0; i < 3; ++i)
widths[i] = get_mbs_width(modes[i]);
if (4 + widths[0] + 6 + widths[1] + 6 + widths[2] + 1 <= info_items_width) {
wmove(mixer_widget.window, 3, info_items_left);
wattrset(mixer_widget.window, attr_mixer_text);
for (i = 0; i < 3; ++i) {
wprintw(mixer_widget.window, "F%c:", '3' + i);
if (has_view_mode && (int)view_mode == i) {
wattrset(mixer_widget.window, attr_mixer_active);
wprintw(mixer_widget.window, "[%s]", modes[i]);
wattrset(mixer_widget.window, attr_mixer_text);
} else {
wprintw(mixer_widget.window, " %s ", modes[i]);
}
if (i < 2)
waddch(mixer_widget.window, ' ');
}
} else {
wattrset(mixer_widget.window, attr_mixer_active);
display_string_in_field(3, info_items_left,
has_view_mode ? modes[view_mode] : "",
info_items_width, ALIGN_LEFT);
}
}
static char *format_gain(long db)
{
if (db != SND_CTL_TLV_DB_GAIN_MUTE)
return casprintf("%.2f", db / 100.0);
else
return cstrdup(_("mute"));
}
static void display_focus_item_info(void)
{
struct control *control;
unsigned int index;
char buf[64];
long db, db2;
int sw, sw2;
char *dbs, *dbs2;
char *value_info;
char *item_info;
int err;
if (!has_info_items)
return;
wattrset(mixer_widget.window, attr_mixer_active);
if (!controls_count || screen_too_small) {
display_string_in_field(4, info_items_left, "", info_items_width, ALIGN_LEFT);
return;
}
control = &controls[focus_control_index];
value_info = NULL;
if (control->flags & TYPE_ENUM) {
err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index);
if (err >= 0)
err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf);
if (err >= 0)
value_info = casprintf(" [%s]", buf);
} else if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
if (control->flags & TYPE_PVOLUME)
get_vol_func = snd_mixer_selem_get_playback_dB;
else
get_vol_func = snd_mixer_selem_get_capture_dB;
if (!(control->flags & HAS_VOLUME_1)) {
err = get_vol_func(control->elem, control->volume_channels[0], &db);
if (err >= 0) {
dbs = format_gain(db);
value_info = casprintf(" [%s %s]", _("dB gain:"), dbs);
free(dbs);
}
} else {
err = get_vol_func(control->elem, control->volume_channels[0], &db);
if (err >= 0)
err = get_vol_func(control->elem, control->volume_channels[1], &db2);
if (err >= 0) {
dbs = format_gain(db);
dbs2 = format_gain(db2);
value_info = casprintf(_(" [%s %s, %s]"), _("dB gain:"), dbs, dbs2);
free(dbs);
free(dbs2);
}
}
} else if (control->flags & TYPE_PSWITCH) {
if (!(control->flags & HAS_PSWITCH_1)) {
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw);
if (err >= 0 && !sw)
value_info = casprintf(" [%s]", _("Off"));
} else {
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw);
if (err >= 0)
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &sw2);
if (err >= 0 && (!sw || !sw2))
value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off"));
}
} else if (control->flags & TYPE_CSWITCH) {
if (!(control->flags & HAS_CSWITCH_1)) {
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw);
if (err >= 0 && !sw)
value_info = casprintf(" [%s]", _("Off"));
} else {
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw);
if (err >= 0)
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &sw2);
if (err >= 0 && (!sw || !sw2))
value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off"));
}
}
item_info = casprintf("%s%s", control->name, value_info ? value_info : "");
free(value_info);
display_string_in_field(4, info_items_left, item_info, info_items_width, ALIGN_LEFT);
free(item_info);
}
static void clear_controls_display(void)
{
int i;
wattrset(mixer_widget.window, attr_mixer_frame);
for (i = 5; i < screen_lines - 1; ++i)
mvwprintw(mixer_widget.window, i, 1, "%*s", screen_cols - 2, "");
}
static void center_string(int line, const char *s)
{
int width = get_mbs_width(s);
if (width <= screen_cols - 2)
mvwaddstr(mixer_widget.window, line, (screen_cols - width) / 2, s);
}
static void display_unplugged(void)
{
int lines, top, left;
bool boojum;
lines = screen_lines - 6;
if (lines < 2)
return;
top = lines / 2;
boojum = lines >= 10 && screen_cols >= 48;
top -= boojum ? 5 : 1;
if (top < 5)
top = 5;
if (boojum) {
left = (screen_cols - 46) / 2;
wattrset(mixer_widget.window, attr_mixer_text);
mvwaddstr(mixer_widget.window, top + 0, left, "In the midst of the word he was trying to say,");
mvwaddstr(mixer_widget.window, top + 1, left + 2, "In the midst of his laughter and glee,");
mvwaddstr(mixer_widget.window, top + 2, left, "He had softly and suddenly vanished away---");
mvwaddstr(mixer_widget.window, top + 3, left + 2, "For the Snark was a Boojum, you see.");
mvwchgat(mixer_widget.window, top + 3, left + 16, 3, /* ^^^ */
attr_mixer_text | A_BOLD, PAIR_NUMBER(attr_mixer_text), NULL);
mvwaddstr(mixer_widget.window, top + 5, left, "(Lewis Carroll, \"The Hunting of the Snark\")");
top += 8;
}
wattrset(mixer_widget.window, attr_errormsg);
center_string(top, _("The sound device was unplugged."));
center_string(top + 1, _("Press F6 to select another sound card."));
}
static void display_no_controls(void)
{
int y;
const char *msg;
y = (screen_lines - 6) / 2 - 1;
if (y < 5)
y = 5;
if (y >= screen_lines - 1)
return;
wattrset(mixer_widget.window, attr_infomsg);
if (view_mode == VIEW_MODE_PLAYBACK && are_there_any_controls())
msg = _("This sound device does not have any playback controls.");
else if (view_mode == VIEW_MODE_CAPTURE && are_there_any_controls())
msg = _("This sound device does not have any capture controls.");
else
msg = _("This sound device does not have any controls.");
center_string(y, msg);
}
static void display_string_centered_in_control(int y, int col, const char *s, int width)
{
int left, x;
left = first_control_x + col * (control_width + 1);
x = left + (control_width - width) / 2;
display_string_in_field(y, x, s, width, ALIGN_CENTER);
}
static void display_control(unsigned int control_index)
{
struct control *control;
int col;
int i;
int left, frame_left;
int bar_height, value;
long volumes[2];
long min, max;
int switches[2];
unsigned int index;
const char *s;
char buf[64];
int err;
control = &controls[control_index];
col = control_index - first_visible_control_index;
left = first_control_x + col * (control_width + 1);
frame_left = left + (control_width - 4) / 2;
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attr_ctl_frame);
else
wattrset(mixer_widget.window, attr_ctl_inactive);
if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
mvwaddch(mixer_widget.window, base_y - volume_height - 1, frame_left, ACS_ULCORNER);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_URCORNER);
for (i = 0; i < volume_height; ++i) {
mvwaddch(mixer_widget.window, base_y - i - 1, frame_left, ACS_VLINE);
mvwaddch(mixer_widget.window, base_y - i - 1, frame_left + 3, ACS_VLINE);
}
mvwaddch(mixer_widget.window, base_y, frame_left,
control->flags & TYPE_PSWITCH ? ACS_LTEE : ACS_LLCORNER);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window,
control->flags & TYPE_PSWITCH ? ACS_RTEE : ACS_LRCORNER);
} else if (control->flags & TYPE_PSWITCH) {
mvwaddch(mixer_widget.window, base_y, frame_left, ACS_ULCORNER);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_URCORNER);
}
if (control->flags & TYPE_PSWITCH) {
mvwaddch(mixer_widget.window, base_y + 1, frame_left, ACS_VLINE);
mvwaddch(mixer_widget.window, base_y + 1, frame_left + 3, ACS_VLINE);
mvwaddch(mixer_widget.window, base_y + 2, frame_left, ACS_LLCORNER);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_LRCORNER);
}
if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
if (control->flags & TYPE_PVOLUME)
get_vol_func = snd_mixer_selem_get_playback_volume;
else
get_vol_func = snd_mixer_selem_get_capture_volume;
err = get_vol_func(control->elem, control->volume_channels[0], &volumes[0]);
if (err >= 0 && (control->flags & HAS_VOLUME_1))
err = get_vol_func(control->elem, control->volume_channels[1], &volumes[1]);
else
volumes[1] = volumes[0];
if (err < 0)
return;
if (control->flags & TYPE_PVOLUME)
err = snd_mixer_selem_get_playback_volume_range(control->elem, &min, &max);
else
err = snd_mixer_selem_get_capture_volume_range(control->elem, &min, &max);
if (err < 0)
return;
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, 0);
bar_height = ((volumes[0] - min) * volume_height + max - min - 1) / (max - min);
for (i = 0; i < volume_height; ++i)
mvwaddch(mixer_widget.window, base_y - i - 1, frame_left + 1,
i + 1 <= bar_height
? ACS_CKBOARD | (control->flags & IS_ACTIVE ? attr_ctl_bar : 0)
: ' ' | (control->flags & IS_ACTIVE ? attr_ctl_frame : 0));
bar_height = ((volumes[1] - min) * volume_height + max - min - 1) / (max - min);
for (i = 0; i < volume_height; ++i)
mvwaddch(mixer_widget.window, base_y - i - 1, frame_left + 2,
i + 1 <= bar_height
? ACS_CKBOARD | (control->flags & IS_ACTIVE ? attr_ctl_bar : 0)
: ' ' | (control->flags & IS_ACTIVE ? attr_ctl_frame : 0));
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attr_mixer_active);
value = ((volumes[0] - min) * 100 + (max - min) / 2) / (max - min);
if (!(control->flags & HAS_VOLUME_1)) {
sprintf(buf, "%d", value);
display_string_in_field(values_y, frame_left - 2, buf, 8, ALIGN_CENTER);
} else {
mvwprintw(mixer_widget.window, values_y, frame_left - 2, "%3d", value);
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attr_ctl_frame);
waddstr(mixer_widget.window, "<>");
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attr_mixer_active);
value = ((volumes[1] - min) * 100 + (max - min) / 2) / (max - min);
wprintw(mixer_widget.window, "%-3d", value);
}
}
if (control->flags & TYPE_PSWITCH) {
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &switches[0]);
if (err >= 0 && (control->flags & HAS_PSWITCH_1))
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &switches[1]);
else
switches[1] = switches[0];
if (err < 0)
return;
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, 0);
mvwaddch(mixer_widget.window, base_y + 1, frame_left + 1,
switches[0]
/* TRANSLATORS: playback on; one character */
? _("O")[0] | (control->flags & IS_ACTIVE ? attr_ctl_nomute : 0)
/* TRANSLATORS: playback muted; one character */
: _("M")[0] | (control->flags & IS_ACTIVE ? attr_ctl_mute : 0));
waddch(mixer_widget.window,
switches[1]
? _("O")[0] | (control->flags & IS_ACTIVE ? attr_ctl_nomute : 0)
: _("M")[0] | (control->flags & IS_ACTIVE ? attr_ctl_mute : 0));
}
if (control->flags & TYPE_CSWITCH) {
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &switches[0]);
if (err >= 0 && (control->flags & HAS_CSWITCH_1))
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &switches[1]);
else
switches[1] = switches[0];
if (err < 0)
return;
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, switches[0] ? attr_ctl_capture : attr_ctl_nocapture);
/* TRANSLATORS: "left"; no more than two characters */
display_string_in_field(cswitch_y - 1, frame_left - 2, switches[0] ? _("L") : "", 2, ALIGN_RIGHT);
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, switches[1] ? attr_ctl_capture : attr_ctl_nocapture);
/* TRANSLATORS: "right"; no more than two characters */
display_string_in_field(cswitch_y - 1, frame_left + 4, switches[1] ? _("R") : "", 2, ALIGN_LEFT);
/* TRANSLATORS: no more than eight characters */
s = _("CAPTURE");
if (switches[0] || switches[1]) {
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attr_ctl_capture);
display_string_in_field(cswitch_y, frame_left - 2, s, 8, ALIGN_CENTER);
} else {
i = get_mbs_width(s);
if (i > 8)
i = 8;
memset(buf, '-', i);
buf[i] = '\0';
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attr_ctl_nocapture);
display_string_in_field(cswitch_y, frame_left - 2, buf, 8, ALIGN_CENTER);
}
}
if (control->flags & TYPE_ENUM) {
err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index);
if (err < 0)
return;
err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf);
if (err < 0)
return;
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attr_mixer_active);
display_string_centered_in_control(base_y, col, buf, control_width);
}
if (control_index == focus_control_index) {
i = first_control_x + col * (control_width + 1) + (control_width - control_name_width) / 2;
wattrset(mixer_widget.window, attr_ctl_mark_focus);
mvwaddch(mixer_widget.window, name_y, i - 1, '<');
mvwaddch(mixer_widget.window, name_y, i + control_name_width, '>');
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attr_ctl_label_focus);
else
wattrset(mixer_widget.window, attr_ctl_label_inactive);
} else {
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attr_ctl_label);
else
wattrset(mixer_widget.window, attr_ctl_label_inactive);
}
display_string_centered_in_control(name_y, col, control->name, control_name_width);
if (channel_name_y > name_y) {
if (control->flags & IS_MULTICH) {
switch (control->flags & MULTICH_MASK) {
case 0:
default:
s = _("Front");
break;
case 1:
s = _("Rear");
break;
case 2:
s = _("Center");
break;
case 3:
s = _("Woofer");
break;
case 4:
s = _("Side");
break;
}
} else {
s = "";
wattrset(mixer_widget.window, attr_mixer_frame);
}
display_string_centered_in_control(channel_name_y, col, s,
control_name_width);
}
}
static void display_scroll_indicators(void)
{
int y0, y1, y;
chtype left, right;
if (screen_too_small)
return;
y0 = screen_lines * 3 / 8;
y1 = screen_lines * 5 / 8;
left = first_visible_control_index > 0 ? ACS_LARROW : ACS_VLINE;
right = first_visible_control_index + visible_controls < controls_count
? ACS_RARROW : ACS_VLINE;
wattrset(mixer_widget.window, attr_mixer_frame);
for (y = y0; y <= y1; ++y) {
mvwaddch(mixer_widget.window, y, 0, left);
mvwaddch(mixer_widget.window, y, screen_cols - 1, right);
}
}
void display_controls(void)
{
unsigned int i;
if (first_visible_control_index > controls_count - visible_controls)
first_visible_control_index = controls_count - visible_controls;
if (first_visible_control_index > focus_control_index)
first_visible_control_index = focus_control_index;
else if (first_visible_control_index < focus_control_index - visible_controls + 1 && visible_controls)
first_visible_control_index = focus_control_index - visible_controls + 1;
clear_controls_display();
display_focus_item_info();
if (controls_count > 0) {
if (!screen_too_small)
for (i = 0; i < visible_controls; ++i)
display_control(first_visible_control_index + i);
} else if (unplugged) {
display_unplugged();
} else if (mixer_device_name) {
display_no_controls();
}
display_scroll_indicators();
controls_changed = FALSE;
}
void compute_controls_layout(void)
{
bool any_volume, any_pswitch, any_cswitch, any_multich;
int max_width, name_len;
int height, space;
unsigned int i;
if (controls_count == 0 || screen_too_small) {
visible_controls = 0;
return;
}
any_volume = FALSE;
any_pswitch = FALSE;
any_cswitch = FALSE;
any_multich = FALSE;
for (i = 0; i < controls_count; ++i) {
if (controls[i].flags & (TYPE_PVOLUME | TYPE_CVOLUME))
any_volume = 1;
if (controls[i].flags & TYPE_PSWITCH)
any_pswitch = 1;
if (controls[i].flags & TYPE_CSWITCH)
any_cswitch = 1;
if (controls[i].flags & IS_MULTICH)
any_multich = 1;
}
max_width = 8;
for (i = 0; i < controls_count; ++i) {
name_len = strlen(controls[i].name);
if (name_len > max_width)
max_width = name_len;
}
max_width = (max_width + 1) & ~1;
control_width = (screen_cols - 3 - (int)controls_count) / controls_count;
if (control_width < 8)
control_width = 8;
if (control_width > max_width)
control_width = max_width;
if (control_width > screen_cols - 4)
control_width = screen_cols - 4;
visible_controls = (screen_cols - 3) / (control_width + 1);
if (visible_controls > controls_count)
visible_controls = controls_count;
first_control_x = 2 + (screen_cols - 3 - visible_controls * (control_width + 1)) / 2;
if (control_width < max_width)
control_name_width = control_width;
else
control_name_width = max_width;
height = 2;
if (any_volume)
height += 2;
if (any_pswitch)
height += 2;
if (any_cswitch)
height += 1;
if (any_multich)
height += 1;
if (any_volume) {
space = screen_lines - 6 - height;
if (space <= 1)
volume_height = 1;
else if (space <= 10)
volume_height = space;
else
volume_height = 10 + (space - 10) / 2;
height += volume_height;
}
space = screen_lines - 6 - height;
channel_name_y = screen_lines - 2 - space / 2;
name_y = channel_name_y - any_multich;
values_y = name_y - any_volume;
cswitch_y = values_y - any_cswitch;
base_y = cswitch_y - 1 - 2 * any_pswitch;
}

10
alsamixer/mixer_display.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef MIXER_DISPLAY_H_INCLUDED
#define MIXER_DISPLAY_H_INCLUDED
void init_mixer_layout(void);
void display_card_info(void);
void display_view_mode(void);
void display_controls(void);
void compute_controls_layout(void);
#endif

680
alsamixer/mixer_widget.c Normal file
View file

@ -0,0 +1,680 @@
/*
* mixer_widget.c - mixer widget and keys handling
* Copyright (c) 1998,1999 Tim Janik <timj@gtk.org>
* Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <alsa/asoundlib.h>
#include "gettext_curses.h"
#include "version.h"
#include "utils.h"
#include "die.h"
#include "mem.h"
#include "colors.h"
#include "widget.h"
#include "textbox.h"
#include "proc_files.h"
#include "card_select.h"
#include "mixer_controls.h"
#include "mixer_display.h"
#include "mixer_widget.h"
snd_mixer_t *mixer;
char *mixer_device_name;
bool unplugged;
struct widget mixer_widget;
enum view_mode view_mode;
int focus_control_index;
snd_mixer_selem_id_t *current_selem_id;
unsigned int current_control_flags;
bool controls_changed;
enum channel_mask {
LEFT = 1,
RIGHT = 2,
};
static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask)
{
if (mask & (SND_CTL_EVENT_MASK_REMOVE |
SND_CTL_EVENT_MASK_INFO |
SND_CTL_EVENT_MASK_VALUE))
controls_changed = TRUE;
return 0;
}
static int mixer_callback(snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem)
{
if (mask & SND_CTL_EVENT_MASK_ADD) {
snd_mixer_elem_set_callback(elem, elem_callback);
controls_changed = TRUE;
}
return 0;
}
void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt)
{
int err;
err = snd_mixer_open(&mixer, 0);
if (err < 0)
fatal_alsa_error(_("cannot open mixer"), err);
mixer_device_name = cstrdup(selem_regopt->device);
err = snd_mixer_selem_register(mixer, selem_regopt, NULL);
if (err < 0)
fatal_alsa_error(_("cannot open mixer"), err);
snd_mixer_set_callback(mixer, mixer_callback);
err = snd_mixer_load(mixer);
if (err < 0)
fatal_alsa_error(_("cannot load mixer controls"), err);
err = snd_mixer_selem_id_malloc(&current_selem_id);
if (err < 0)
fatal_error("out of memory");
}
static void set_view_mode(enum view_mode m)
{
view_mode = m;
create_controls();
}
static void close_hctl(void)
{
free_controls();
if (mixer_device_name) {
snd_mixer_detach(mixer, mixer_device_name);
free(mixer_device_name);
mixer_device_name = NULL;
}
}
static void check_unplugged(void)
{
snd_hctl_t *hctl;
snd_ctl_t *ctl;
unsigned int state;
int err;
unplugged = FALSE;
if (mixer_device_name) {
err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
if (err >= 0) {
ctl = snd_hctl_ctl(hctl);
/* just any random function that does an ioctl() */
err = snd_ctl_get_power_state(ctl, &state);
if (err == -ENODEV)
unplugged = TRUE;
}
}
}
void close_mixer_device(void)
{
check_unplugged();
close_hctl();
display_card_info();
set_view_mode(view_mode);
}
bool select_card_by_name(const char *device_name)
{
int err;
bool opened;
char *msg;
close_hctl();
unplugged = FALSE;
opened = FALSE;
if (device_name) {
err = snd_mixer_attach(mixer, device_name);
if (err >= 0)
opened = TRUE;
else {
msg = casprintf(_("Cannot open mixer device '%s'."), device_name);
show_alsa_error(msg, err);
free(msg);
}
}
if (opened) {
mixer_device_name = cstrdup(device_name);
err = snd_mixer_load(mixer);
if (err < 0)
fatal_alsa_error(_("cannot load mixer controls"), err);
}
display_card_info();
set_view_mode(view_mode);
return opened;
}
static void show_help(void)
{
const char *help[] = {
_("Esc Exit"),
_("F1 ? H Help"),
_("F2 / System information"),
_("F3 Show playback controls"),
_("F4 Show capture controls"),
_("F5 Show all controls"),
_("Tab Toggle view mode (F3/F4/F5)"),
_("F6 S Select sound card"),
_("L Redraw screen"),
"",
_("Left Move to the previous control"),
_("Right Move to the next control"),
"",
_("Up/Down Change volume"),
_("+ - Change volume"),
_("Page Up/Dn Change volume in big steps"),
_("End Set volume to 0%"),
_("0-9 Set volume to 0%-90%"),
_("Q W E Increase left/both/right volumes"),
/* TRANSLATORS: or Y instead of Z */
_("Z X C Decrease left/both/right volumes"),
_("B Balance left and right volumes"),
"",
_("M Toggle mute"),
/* TRANSLATORS: or , . */
_("< > Toggle left/right mute"),
"",
_("Space Toggle capture"),
/* TRANSLATORS: or Insert Delete */
_("; ' Toggle left/right capture"),
"",
_("Authors:"),
_(" Tim Janik <timj@gtk.org>"),
_(" Jaroslav Kysela <perex@perex.cz>"),
_(" Clemens Ladisch <clemens@ladisch.de>"),
};
show_text(help, ARRAY_SIZE(help), _("Help"));
}
void refocus_control(void)
{
if (focus_control_index < controls_count) {
snd_mixer_selem_get_id(controls[focus_control_index].elem, current_selem_id);
current_control_flags = controls[focus_control_index].flags;
}
display_controls();
}
static struct control *get_focus_control(unsigned int type)
{
if (focus_control_index >= 0 &&
focus_control_index < controls_count &&
(controls[focus_control_index].flags & IS_ACTIVE) &&
(controls[focus_control_index].flags & type))
return &controls[focus_control_index];
else
return NULL;
}
static void change_enum_to_percent(struct control *control, int value)
{
unsigned int i;
unsigned int index;
unsigned int new_index;
int items;
int err;
i = ffs(control->enum_channel_bits) - 1;
err = snd_mixer_selem_get_enum_item(control->elem, i, &index);
if (err < 0)
return;
new_index = index;
if (value == 0) {
new_index = 0;
} else if (value == 100) {
items = snd_mixer_selem_get_enum_items(control->elem);
if (items < 1)
return;
new_index = items - 1;
}
if (new_index == index)
return;
for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
if (control->enum_channel_bits & (1 << i))
snd_mixer_selem_set_enum_item(control->elem, i, new_index);
}
static void change_enum_relative(struct control *control, int delta)
{
int items;
unsigned int i;
unsigned int index;
int new_index;
int err;
items = snd_mixer_selem_get_enum_items(control->elem);
if (items < 1)
return;
err = snd_mixer_selem_get_enum_item(control->elem, 0, &index);
if (err < 0)
return;
new_index = (int)index + delta;
if (new_index < 0)
new_index = 0;
else if (new_index >= items)
new_index = items - 1;
if (new_index == index)
return;
for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
if (control->enum_channel_bits & (1 << i))
snd_mixer_selem_set_enum_item(control->elem, i, new_index);
}
static void change_volume_to_percent(struct control *control, int value, unsigned int channels)
{
int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
long min, max;
int err;
if (!(control->flags & HAS_VOLUME_1))
channels = LEFT;
if (control->flags & TYPE_PVOLUME) {
get_range_func = snd_mixer_selem_get_playback_volume_range;
set_func = snd_mixer_selem_set_playback_volume;
} else {
get_range_func = snd_mixer_selem_get_capture_volume_range;
set_func = snd_mixer_selem_set_capture_volume;
}
err = get_range_func(control->elem, &min, &max);
if (err < 0)
return;
if (channels & LEFT)
set_func(control->elem, control->volume_channels[0], min + (max - min) * value / 100);
if (channels & RIGHT)
set_func(control->elem, control->volume_channels[1], min + (max - min) * value / 100);
}
static void change_volume_relative(struct control *control, int delta, unsigned int channels)
{
int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
long min, max;
long left, right;
long value;
int err;
if (!(control->flags & HAS_VOLUME_1))
channels = LEFT;
if (control->flags & TYPE_PVOLUME) {
get_range_func = snd_mixer_selem_get_playback_volume_range;
get_func = snd_mixer_selem_get_playback_volume;
set_func = snd_mixer_selem_set_playback_volume;
} else {
get_range_func = snd_mixer_selem_get_capture_volume_range;
get_func = snd_mixer_selem_get_capture_volume;
set_func = snd_mixer_selem_set_capture_volume;
}
err = get_range_func(control->elem, &min, &max);
if (err < 0)
return;
if (channels & LEFT) {
err = get_func(control->elem, control->volume_channels[0], &left);
if (err < 0)
return;
}
if (channels & RIGHT) {
err = get_func(control->elem, control->volume_channels[1], &right);
if (err < 0)
return;
}
if (channels & LEFT) {
value = left + delta;
if (value < min)
value = min;
else if (value > max)
value = max;
if (value != left)
set_func(control->elem, control->volume_channels[0], value);
}
if (channels & RIGHT) {
value = right + delta;
if (value < min)
value = min;
else if (value > max)
value = max;
if (value != right)
set_func(control->elem, control->volume_channels[1], value);
}
}
static void change_control_to_percent(int value, unsigned int channels)
{
struct control *control;
control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
if (!control)
return;
if (control->flags & TYPE_ENUM)
change_enum_to_percent(control, value);
else
change_volume_to_percent(control, value, channels);
display_controls();
}
static void change_control_relative(int delta, unsigned int channels)
{
struct control *control;
control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
if (!control)
return;
if (control->flags & TYPE_ENUM)
change_enum_relative(control, delta);
else
change_volume_relative(control, delta, channels);
display_controls();
}
static void toggle_switches(unsigned int type, unsigned int channels)
{
struct control *control;
unsigned int switch_1_mask;
int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *);
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int);
snd_mixer_selem_channel_id_t channel_ids[2];
int left, right;
int err;
control = get_focus_control(type);
if (!control)
return;
if (type == TYPE_PSWITCH) {
switch_1_mask = HAS_PSWITCH_1;
get_func = snd_mixer_selem_get_playback_switch;
set_func = snd_mixer_selem_set_playback_switch;
channel_ids[0] = control->pswitch_channels[0];
channel_ids[1] = control->pswitch_channels[1];
} else {
switch_1_mask = HAS_CSWITCH_1;
get_func = snd_mixer_selem_get_capture_switch;
set_func = snd_mixer_selem_set_capture_switch;
channel_ids[0] = control->cswitch_channels[0];
channel_ids[1] = control->cswitch_channels[1];
}
if (!(control->flags & switch_1_mask))
channels = LEFT;
if (channels & LEFT) {
err = get_func(control->elem, channel_ids[0], &left);
if (err < 0)
return;
}
if (channels & RIGHT) {
err = get_func(control->elem, channel_ids[1], &right);
if (err < 0)
return;
}
if (channels & LEFT)
set_func(control->elem, channel_ids[0], !left);
if (channels & RIGHT)
set_func(control->elem, channel_ids[1], !right);
display_controls();
}
static void toggle_mute(unsigned int channels)
{
toggle_switches(TYPE_PSWITCH, channels);
}
static void toggle_capture(unsigned int channels)
{
toggle_switches(TYPE_CSWITCH, channels);
}
static void balance_volumes(void)
{
struct control *control;
long left, right;
int err;
control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME);
if (!control || !(control->flags & HAS_VOLUME_1))
return;
if (control->flags & TYPE_PVOLUME) {
err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[0], &left);
if (err < 0)
return;
err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[1], &right);
if (err < 0)
return;
} else {
err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[0], &left);
if (err < 0)
return;
err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[1], &right);
if (err < 0)
return;
}
left = (left + right) / 2;
if (control->flags & TYPE_PVOLUME) {
snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[0], left);
snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[1], left);
} else {
snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[0], left);
snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[1], left);
}
display_controls();
}
static void on_handle_key(int key)
{
switch (key) {
case 27:
case KEY_CANCEL:
case KEY_F(10):
mixer_widget.close();
break;
case KEY_F(1):
case KEY_HELP:
case 'H':
case 'h':
case '?':
show_help();
break;
case KEY_F(2):
case '/':
create_proc_files_list();
break;
case KEY_F(3):
set_view_mode(VIEW_MODE_PLAYBACK);
break;
case KEY_F(4):
set_view_mode(VIEW_MODE_CAPTURE);
break;
case KEY_F(5):
set_view_mode(VIEW_MODE_ALL);
break;
case '\t':
set_view_mode((enum view_mode)((view_mode + 1) % VIEW_MODE_COUNT));
break;
case KEY_F(6):
case 'S':
case 's':
create_card_select_list();
break;
case KEY_REFRESH:
case 12:
case 'L':
case 'l':
clearok(mixer_widget.window, TRUE);
display_controls();
break;
case KEY_LEFT:
case 'P':
case 'p':
if (focus_control_index > 0) {
--focus_control_index;
refocus_control();
}
break;
case KEY_RIGHT:
case 'N':
case 'n':
if (focus_control_index < controls_count - 1) {
++focus_control_index;
refocus_control();
}
break;
case KEY_PPAGE:
change_control_relative(5, LEFT | RIGHT);
break;
case KEY_NPAGE:
change_control_relative(-5, LEFT | RIGHT);
break;
#if 0
case KEY_BEG:
case KEY_HOME:
change_control_to_percent(100, LEFT | RIGHT);
break;
#endif
case KEY_LL:
case KEY_END:
change_control_to_percent(0, LEFT | RIGHT);
break;
case KEY_UP:
case '+':
case 'K':
case 'k':
case 'W':
case 'w':
change_control_relative(1, LEFT | RIGHT);
break;
case KEY_DOWN:
case '-':
case 'J':
case 'j':
case 'X':
case 'x':
change_control_relative(-1, LEFT | RIGHT);
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
change_control_to_percent((key - '0') * 10, LEFT | RIGHT);
break;
case 'Q':
case 'q':
change_control_relative(1, LEFT);
break;
case 'Y':
case 'y':
case 'Z':
case 'z':
change_control_relative(-1, LEFT);
break;
case 'E':
case 'e':
change_control_relative(1, RIGHT);
break;
case 'C':
case 'c':
change_control_relative(-1, RIGHT);
break;
case 'M':
case 'm':
toggle_mute(LEFT | RIGHT);
break;
case 'B':
case 'b':
case '=':
balance_volumes();
break;
case '<':
case ',':
toggle_mute(LEFT);
break;
case '>':
case '.':
toggle_mute(RIGHT);
break;
case ' ':
toggle_capture(LEFT | RIGHT);
break;
case KEY_IC:
case ';':
toggle_capture(LEFT);
break;
case KEY_DC:
case '\'':
toggle_capture(RIGHT);
break;
}
}
static void create(void)
{
static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " ";
widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0,
attr_mixer_frame, WIDGET_BORDER);
if (screen_cols >= (sizeof(title) - 1) + 2) {
wattrset(mixer_widget.window, attr_mixer_active);
mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title);
}
init_mixer_layout();
display_card_info();
set_view_mode(view_mode);
}
static void on_window_size_changed(void)
{
create();
}
static void on_close(void)
{
widget_free(&mixer_widget);
}
void mixer_shutdown(void)
{
free_controls();
if (mixer)
snd_mixer_close(mixer);
if (current_selem_id)
snd_mixer_selem_id_free(current_selem_id);
}
struct widget mixer_widget = {
.handle_key = on_handle_key,
.window_size_changed = on_window_size_changed,
.close = on_close,
};
void create_mixer_widget(void)
{
create();
}

36
alsamixer/mixer_widget.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef MIXER_WIDGET_H_INCLUDED
#define MIXER_WIDGET_H_INCLUDED
#include CURSESINC
#include <alsa/asoundlib.h>
#include "widget.h"
enum view_mode {
VIEW_MODE_PLAYBACK,
VIEW_MODE_CAPTURE,
VIEW_MODE_ALL,
VIEW_MODE_COUNT,
};
extern snd_mixer_t *mixer;
extern char *mixer_device_name;
extern bool unplugged;
extern struct widget mixer_widget;
extern enum view_mode view_mode;
extern int focus_control_index;
extern snd_mixer_selem_id_t *current_selem_id;
extern unsigned int current_control_flags;
extern bool controls_changed;
void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt);
void create_mixer_widget(void);
void mixer_shutdown(void);
void close_mixer_device(void);
bool select_card_by_name(const char *device_name);
void refocus_control(void);
#endif

169
alsamixer/proc_files.c Normal file
View file

@ -0,0 +1,169 @@
/*
* proc_files.c - shows ALSA system information files
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <assert.h>
#include <menu.h>
#include <unistd.h>
#include "gettext_curses.h"
#include "utils.h"
#include "die.h"
#include "mem.h"
#include "colors.h"
#include "widget.h"
#include "textbox.h"
#include "proc_files.h"
static struct widget proc_widget;
static ITEM *items[7];
static unsigned int items_count;
static MENU *menu;
static void on_menu_key(int key)
{
static const struct {
int key;
int request;
} key_map[] = {
{ KEY_DOWN, REQ_DOWN_ITEM },
{ KEY_UP, REQ_UP_ITEM },
{ KEY_HOME, REQ_FIRST_ITEM },
{ KEY_NPAGE, REQ_SCR_DPAGE },
{ KEY_PPAGE, REQ_SCR_UPAGE },
{ KEY_BEG, REQ_FIRST_ITEM },
{ KEY_END, REQ_LAST_ITEM },
};
unsigned int i;
for (i = 0; i < ARRAY_SIZE(key_map); ++i)
if (key_map[i].key == key) {
menu_driver(menu, key_map[i].request);
break;
}
}
static void on_handle_key(int key)
{
ITEM *item;
switch (key) {
case 27:
case KEY_CANCEL:
proc_widget.close();
break;
case 10:
case 13:
case KEY_ENTER:
item = current_item(menu);
if (item)
show_textfile(item_name(item));
break;
default:
on_menu_key(key);
break;
}
}
static bool create(void)
{
int rows, columns;
const char *title;
if (screen_lines < 3 || screen_cols < 20) {
proc_widget.close();
beep();
return FALSE;
}
scale_menu(menu, &rows, &columns);
rows += 2;
columns += 2;
if (rows > screen_lines)
rows = screen_lines;
if (columns > screen_cols)
columns = screen_cols;
widget_init(&proc_widget, rows, columns, SCREEN_CENTER, SCREEN_CENTER,
attr_menu, WIDGET_BORDER | WIDGET_SUBWINDOW);
title = _("Select File");
mvwprintw(proc_widget.window, 0, (columns - 2 - get_mbs_width(title)) / 2, " %s ", title);
set_menu_win(menu, proc_widget.window);
set_menu_sub(menu, proc_widget.subwindow);
return TRUE;
}
static void on_window_size_changed(void)
{
unpost_menu(menu);
if (!create())
return;
post_menu(menu);
}
static void on_close(void)
{
unsigned int i;
unpost_menu(menu);
free_menu(menu);
for (i = 0; i < items_count; ++i)
free_item(items[i]);
widget_free(&proc_widget);
}
static void add_item(const char *file_name)
{
if (access(file_name, F_OK) == 0) {
items[items_count] = new_item(file_name, NULL);
if (!items[items_count])
fatal_error("cannot create menu item");
++items_count;
assert(items_count < ARRAY_SIZE(items));
}
}
static struct widget proc_widget = {
.handle_key = on_handle_key,
.window_size_changed = on_window_size_changed,
.close = on_close,
};
void create_proc_files_list(void)
{
items_count = 0;
add_item("/proc/asound/version");
add_item("/proc/asound/cards");
add_item("/proc/asound/devices");
add_item("/proc/asound/oss/devices");
add_item("/proc/asound/timers");
add_item("/proc/asound/pcm");
items[items_count] = NULL;
menu = new_menu(items);
if (!menu)
fatal_error("cannot create menu");
set_menu_fore(menu, attr_menu_selected);
set_menu_back(menu, attr_menu);
set_menu_mark(menu, NULL);
menu_opts_off(menu, O_SHOWDESC);
if (!create())
return;
post_menu(menu);
}

6
alsamixer/proc_files.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef PROC_FILES_H_INCLUDED
#define PROC_FILES_H_INCLUDED
void create_proc_files_list(void);
#endif

396
alsamixer/textbox.c Normal file
View file

@ -0,0 +1,396 @@
/*
* textbox.c - show a text box for messages, files or help
* Copyright (c) 1998,1999 Tim Janik <timj@gtk.org>
* Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include CURSESINC
#include <alsa/asoundlib.h>
#include "gettext_curses.h"
#include "utils.h"
#include "die.h"
#include "mem.h"
#include "colors.h"
#include "widget.h"
#include "textbox.h"
#define MAX_FILE_SIZE 1048576
static void create_text_box(const char *const *lines, unsigned int count,
const char *title, int attrs);
void show_error(const char *msg, int err)
{
const char *lines[2];
unsigned int count;
lines[0] = msg;
count = 1;
if (err) {
lines[1] = strerror(err);
count = 2;
}
create_text_box(lines, count, _("Error"), attr_errormsg);
}
void show_alsa_error(const char *msg, int err)
{
const char *lines[2];
unsigned int count;
lines[0] = msg;
count = 1;
if (err < 0) {
lines[1] = snd_strerror(err);
count = 2;
}
create_text_box(lines, count, _("Error"), attr_errormsg);
}
static char *read_file(const char *file_name, unsigned int *file_size)
{
FILE *f;
int err;
char *buf;
unsigned int allocated = 2048;
unsigned int bytes_read;
f = fopen(file_name, "r");
if (!f) {
err = errno;
buf = casprintf(_("Cannot open file \"%s\"."), file_name);
show_error(buf, err);
free(buf);
return NULL;
}
*file_size = 0;
do {
allocated *= 2;
buf = crealloc(buf, allocated);
bytes_read = fread(buf + *file_size, 1, allocated - *file_size, f);
*file_size += bytes_read;
} while (*file_size == allocated && allocated < MAX_FILE_SIZE);
fclose(f);
if (*file_size > 0 && buf[*file_size - 1] != '\n' && *file_size < allocated) {
buf[*file_size] = '\n';
++*file_size;
}
return buf;
}
void show_textfile(const char *file_name)
{
char *buf;
unsigned int file_size;
unsigned int line_count;
unsigned int i;
const char **lines;
const char *start_line;
buf = read_file(file_name, &file_size);
if (!buf)
return;
line_count = 0;
for (i = 0; i < file_size; ++i)
line_count += buf[i] == '\n';
lines = ccalloc(line_count, sizeof *lines);
line_count = 0;
start_line = buf;
for (i = 0; i < file_size; ++i) {
if (buf[i] == '\n') {
lines[line_count++] = start_line;
buf[i] = '\0';
start_line = &buf[i + 1];
}
if (buf[i] == '\t')
buf[i] = ' ';
}
create_text_box(lines, line_count, file_name, attr_textbox);
free(lines);
free(buf);
}
void show_text(const char *const *lines, unsigned int count, const char *title)
{
create_text_box(lines, count, title, attr_textbox);
}
/**********************************************************************/
static struct widget text_widget;
static char *title;
static int widget_attrs;
static char **text_lines;
static unsigned int text_lines_count;
static int max_line_width;
static int text_box_y;
static int text_box_x;
static int max_scroll_y;
static int max_scroll_x;
static int current_top;
static int current_left;
static void update_text_lines(void)
{
int i;
int width;
const char *line_begin;
const char *line_end;
int cur_y, cur_x;
int rest_of_line;
for (i = 0; i < text_box_y; ++i) {
width = current_left;
line_begin = mbs_at_width(text_lines[current_top + i], &width, 1);
wmove(text_widget.window, i + 1, 1);
if (width > current_left)
waddch(text_widget.window, ' ');
if (*line_begin != '\0') {
width = text_box_x;
line_end = mbs_at_width(line_begin, &width, -1);
if (width)
waddnstr(text_widget.window, line_begin,
line_end - line_begin);
}
getyx(text_widget.window, cur_y, cur_x);
if (cur_y == i + 1) {
rest_of_line = text_box_x + 1 - cur_x;
if (rest_of_line > 0)
wprintw(text_widget.window, "%*s", rest_of_line, "");
}
}
}
static void update_y_scroll_bar(void)
{
int length;
int begin, end;
int i;
if (max_scroll_y <= 0 || text_lines_count == 0)
return;
length = text_box_y * text_box_y / text_lines_count;
if (length >= text_box_y)
return;
begin = current_top * (text_box_y - length) / max_scroll_y;
end = begin + length;
for (i = 0; i < text_box_y; ++i)
mvwaddch(text_widget.window, i + 1, text_box_x + 1,
i >= begin && i < end ? ACS_BOARD : ' ');
}
static void update_x_scroll_bar(void)
{
int length;
int begin, end;
int i;
if (max_scroll_x <= 0 || max_line_width <= 0)
return;
length = text_box_x * text_box_x / max_line_width;
if (length >= text_box_x)
return;
begin = current_left * (text_box_x - length) / max_scroll_x;
end = begin + length;
wmove(text_widget.window, text_box_y + 1, 1);
for (i = 0; i < text_box_x; ++i)
waddch(text_widget.window, i >= begin && i < end ? ACS_BOARD : ' ');
}
static void move_x(int delta)
{
int left;
left = current_left + delta;
if (left < 0)
left = 0;
else if (left > max_scroll_x)
left = max_scroll_x;
if (left != current_left) {
current_left = left;
update_text_lines();
update_x_scroll_bar();
}
}
static void move_y(int delta)
{
int top;
top = current_top + delta;
if (top < 0)
top = 0;
else if (top > max_scroll_y)
top = max_scroll_y;
if (top != current_top) {
current_top = top;
update_text_lines();
update_y_scroll_bar();
}
}
static void on_handle_key(int key)
{
switch (key) {
case 10:
case 13:
case 27:
case KEY_CANCEL:
case KEY_ENTER:
case KEY_CLOSE:
case KEY_EXIT:
text_widget.close();
break;
case KEY_DOWN:
case KEY_SF:
case 'J':
case 'j':
case 'X':
case 'x':
move_y(1);
break;
case KEY_UP:
case KEY_SR:
case 'K':
case 'k':
case 'W':
case 'w':
move_y(-1);
break;
case KEY_LEFT:
case 'H':
case 'h':
case 'P':
case 'p':
move_x(-1);
break;
case KEY_RIGHT:
case 'L':
case 'l':
case 'N':
case 'n':
move_x(1);
break;
case KEY_NPAGE:
case ' ':
move_y(text_box_y);
break;
case KEY_PPAGE:
case KEY_BACKSPACE:
case 'B':
case 'b':
move_y(-text_box_y);
break;
case KEY_HOME:
case KEY_BEG:
move_x(-max_scroll_x);
break;
case KEY_LL:
case KEY_END:
move_x(max_scroll_x);
break;
case '\t':
move_x(8);
break;
case KEY_BTAB:
move_x(-8);
break;
}
}
static bool create(void)
{
int len, width;
if (screen_lines < 3 || screen_cols < 8) {
text_widget.close();
beep();
return FALSE;
}
width = max_line_width;
len = get_mbs_width(title) + 2;
if (width < len)
width = len;
text_box_y = text_lines_count;
if (text_box_y > screen_lines - 2)
text_box_y = screen_lines - 2;
max_scroll_y = text_lines_count - text_box_y;
text_box_x = width;
if (text_box_x > screen_cols - 2)
text_box_x = screen_cols - 2;
max_scroll_x = max_line_width - text_box_x;
widget_init(&text_widget, text_box_y + 2, text_box_x + 2,
SCREEN_CENTER, SCREEN_CENTER, widget_attrs, WIDGET_BORDER);
mvwprintw(text_widget.window, 0, (text_box_x + 2 - get_mbs_width(title) - 2) / 2, " %s ", title);
if (current_top > max_scroll_y)
current_top = max_scroll_y;
if (current_left > max_scroll_x)
current_left = max_scroll_x;
update_text_lines();
update_y_scroll_bar();
update_x_scroll_bar();
return TRUE;
}
static void on_window_size_changed(void)
{
create();
}
static void on_close(void)
{
unsigned int i;
for (i = 0; i < text_lines_count; ++i)
free(text_lines[i]);
free(text_lines);
widget_free(&text_widget);
}
static struct widget text_widget = {
.handle_key = on_handle_key,
.window_size_changed = on_window_size_changed,
.close = on_close,
};
static void create_text_box(const char *const *lines, unsigned int count,
const char *title_, int attrs)
{
unsigned int i;
text_lines = ccalloc(count, sizeof *text_lines);
for (i = 0; i < count; ++i)
text_lines[i] = cstrdup(lines[i]);
text_lines_count = count;
max_line_width = get_max_mbs_width(lines, count);
title = cstrdup(title_);
widget_attrs = attrs;
current_top = 0;
current_left = 0;
create();
}

10
alsamixer/textbox.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef TEXTBOX_H_INCLUDED
#define TEXTBOX_H_INCLUDED
void show_error(const char *msg, int err);
void show_alsa_error(const char *msg, int err);
void show_text(const char *const *text_lines, unsigned int count,
const char *title);
void show_textfile(const char *file_name);
#endif

111
alsamixer/utils.c Normal file
View file

@ -0,0 +1,111 @@
/*
* utils.c - multibyte-string helpers
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _XOPEN_SOURCE
#include "aconfig.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include "utils.h"
/*
* mbs_at_width - compute screen position in a string
*
* For displaying strings on the screen, we have to know how many character
* cells are occupied. This function calculates the position in a multibyte
* string that is at a desired position.
*
* Parameters:
* s: the string
* width: on input, the desired number of character cells; on output, the actual
* position, in character cells, of the return value
* dir: -1 or 1; in which direction to round if a multi-column character goes
* over the desired width
*
* Return value:
* Pointer to the place in the string that is as near the desired width as
* possible. If the string is too short, the return value points to the
* terminating zero. If the last character is a multi-column character that
* goes over the desired width, the return value may be one character cell
* earlier or later than desired, depending on the dir parameter.
* In any case, the return value points after any zero-width characters that
* follow the last character.
*/
const char *mbs_at_width(const char *s, int *width, int dir)
{
size_t len;
wchar_t wc;
int bytes;
int width_so_far, w;
if (*width <= 0)
return s;
mbtowc(NULL, NULL, 0); /* reset shift state */
len = strlen(s);
width_so_far = 0;
while (len && (bytes = mbtowc(&wc, s, len)) > 0) {
w = wcwidth(wc);
if (width_so_far + w > *width && dir < 0)
break;
if (w >= 0)
width_so_far += w;
s += bytes;
len -= bytes;
if (width_so_far >= *width) {
while (len && (bytes = mbtowc(&wc, s, len)) > 0) {
w = wcwidth(wc);
if (w != 0)
break;
s += bytes;
len -= bytes;
}
break;
}
}
*width = width_so_far;
return s;
}
/*
* get_mbs_width - compute screen width of a string
*/
unsigned int get_mbs_width(const char *s)
{
int width;
width = INT_MAX;
mbs_at_width(s, &width, 1);
return width;
}
/*
* get_max_mbs_width - get width of longest string in an array
*/
unsigned int get_max_mbs_width(const char *const *s, unsigned int count)
{
unsigned int max_width, i, len;
max_width = 0;
for (i = 0; i < count; ++i) {
len = get_mbs_width(s[i]);
if (len > max_width)
max_width = len;
}
return max_width;
}

10
alsamixer/utils.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef UTILS_H_INCLUDED
#define UTILS_H_INCLUDED
#define ARRAY_SIZE(a) (sizeof(a) / sizeof *(a))
unsigned int get_mbs_width(const char *s);
unsigned int get_max_mbs_width(const char *const *s, unsigned int count);
const char *mbs_at_width(const char *s, int *width, int dir);
#endif

140
alsamixer/widget.c Normal file
View file

@ -0,0 +1,140 @@
/*
* widget.c - handles widget objects and the widget stack
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdlib.h>
#include <term.h>
#include "die.h"
#include "widget.h"
int screen_lines;
int screen_cols;
static int cursor_visibility = -1;
static void widget_handle_key(int key)
{
}
static void update_cursor_visibility(void)
{
struct widget *active_widget;
active_widget = get_active_widget();
if (active_widget &&
active_widget->cursor_visibility != cursor_visibility) {
cursor_visibility = active_widget->cursor_visibility;
curs_set(cursor_visibility);
}
}
void widget_init(struct widget *widget, int lines_, int cols, int y, int x,
chtype bkgd, unsigned int flags)
{
WINDOW *old_window;
if (y == SCREEN_CENTER)
y = (screen_lines - lines_) / 2;
if (x == SCREEN_CENTER)
x = (screen_cols - cols) / 2;
old_window = widget->window;
widget->window = newwin(lines_, cols, y, x);
if (!widget->window)
fatal_error("cannot create window");
keypad(widget->window, TRUE);
nodelay(widget->window, TRUE);
leaveok(widget->window, !(flags & WIDGET_CURSOR_VISIBLE));
wbkgdset(widget->window, bkgd);
werase(widget->window);
if (flags & WIDGET_BORDER)
box(widget->window, 0, 0);
if (flags & WIDGET_SUBWINDOW) {
if (widget->subwindow)
delwin(widget->subwindow);
widget->subwindow = derwin(widget->window,
lines_ - 2, cols - 2, 1, 1);
if (!widget->subwindow)
fatal_error("cannot create subwindow");
wbkgdset(widget->subwindow, bkgd);
}
widget->cursor_visibility = !!(flags & WIDGET_CURSOR_VISIBLE);
if (widget->panel) {
replace_panel(widget->panel, widget->window);
} else {
widget->panel = new_panel(widget->window);
if (!widget->panel)
fatal_error("cannot create panel");
set_panel_userptr(widget->panel, widget);
}
if (!widget->handle_key)
widget->handle_key = widget_handle_key;
if (old_window)
delwin(old_window);
update_cursor_visibility();
}
void widget_free(struct widget *widget)
{
if (widget->panel) {
del_panel(widget->panel);
widget->panel = NULL;
}
if (widget->subwindow) {
delwin(widget->subwindow);
widget->subwindow = NULL;
}
if (widget->window) {
delwin(widget->window);
widget->window = NULL;
}
update_cursor_visibility();
}
struct widget *get_active_widget(void)
{
PANEL *active_panel;
active_panel = panel_below(NULL);
if (active_panel)
return panel_userptr(active_panel);
else
return NULL;
}
void window_size_changed(void)
{
PANEL *panel, *below;
struct widget *widget;
getmaxyx(stdscr, screen_lines, screen_cols);
if (tigetflag("xenl") != 1 && tigetflag("am") != 1)
--screen_lines;
for (panel = panel_below(NULL); panel; panel = below) {
below = panel_below(panel);
widget = panel_userptr(panel);
widget->window_size_changed();
}
}

33
alsamixer/widget.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef WIDGET_H_INCLUDED
#define WIDGET_H_INCLUDED
#include <panel.h>
#define WIDGET_BORDER 0x1
#define WIDGET_SUBWINDOW 0x2
#define WIDGET_CURSOR_VISIBLE 0x4
#define SCREEN_CENTER -1
struct widget {
WINDOW *window;
WINDOW *subwindow; /* optional: contents without border */
PANEL *panel;
int cursor_visibility;
void (*handle_key)(int key);
void (*window_size_changed)(void);
void (*close)(void);
};
extern int screen_lines;
extern int screen_cols;
void widget_init(struct widget *widget,
int lines_, int cols, int y, int x,
chtype bkgd, unsigned int flags);
void widget_free(struct widget *widget);
struct widget *get_active_widget(void);
void window_size_changed(void);
#endif

View file

@ -1,6 +1,6 @@
dnl Process this file with autoconf to produce a configure script. dnl Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59) AC_PREREQ(2.59)
AC_INIT(alsamixer/alsamixer.c) AC_INIT(aplay/aplay.c)
AC_PREFIX_DEFAULT(/usr) AC_PREFIX_DEFAULT(/usr)
AM_INIT_AUTOMAKE(alsa-utils, 1.0.20) AM_INIT_AUTOMAKE(alsa-utils, 1.0.20)
@ -105,23 +105,32 @@ if test x$alsamixer = xtrue; then
[ --with-curses libname Specify the curses library to use (default=auto)], [ --with-curses libname Specify the curses library to use (default=auto)],
curseslib="$withval", curseslib="$withval",
curseslib="auto") curseslib="auto")
if test "$curseslib" = "ncursesw"; then CURSESLIBDIR=""
NCURSESLIBSUFFIX=""
CURSES_NLS="no"
if test "$curseslib" = "ncursesw" -o \( "$curseslib" = "auto" -a "$USE_NLS" = "yes" \); then
AC_CHECK_PROG([ncursesw5_config], [ncursesw5-config], [yes]) AC_CHECK_PROG([ncursesw5_config], [ncursesw5-config], [yes])
if test "$ncursesw5_config" = "yes"; then if test "$ncursesw5_config" = "yes"; then
CURSESINC="<ncurses.h>" CURSESINC="<ncurses.h>"
CURSESLIB=`ncursesw5-config --libs` CURSESLIB=`ncursesw5-config --libs`
CURSESLIBDIR=`ncursesw5-config --libdir`
CURSES_CFLAGS=`ncursesw5-config --cflags` CURSES_CFLAGS=`ncursesw5-config --cflags`
curseslib="ncursesw" curseslib="ncursesw"
else else
AC_CHECK_LIB(ncursesw, initscr, AC_CHECK_LIB(ncursesw, initscr,
[ CURSESINC='<ncurses.h>'; CURSESLIB='-lncursesw'; curseslib="ncursesw"]) [ CURSESINC='<ncurses.h>'; CURSESLIB='-lncursesw'; curseslib="ncursesw"])
fi fi
if test -n "$CURSESINC"; then
NCURSESLIBSUFFIX="w"
CURSES_NLS="yes"
fi
fi fi
if test "$curseslib" = "ncurses" -o "$curseslib" = "auto"; then if test "$curseslib" = "ncurses" -o "$curseslib" = "auto"; then
AC_CHECK_PROG([ncurses5_config], [ncurses5-config], [yes]) AC_CHECK_PROG([ncurses5_config], [ncurses5-config], [yes])
if test "$ncurses5_config" = "yes"; then if test "$ncurses5_config" = "yes"; then
CURSESINC="<ncurses.h>" CURSESINC="<ncurses.h>"
CURSESLIB=`ncurses5-config --libs` CURSESLIB=`ncurses5-config --libs`
CURSESLIBDIR=`ncurses5-config --libdir`
CURSES_CFLAGS=`ncurses5-config --cflags` CURSES_CFLAGS=`ncurses5-config --cflags`
curseslib="ncurses" curseslib="ncurses"
else else
@ -136,6 +145,78 @@ if test x$alsamixer = xtrue; then
if test -z "$CURSESINC"; then if test -z "$CURSESINC"; then
AC_MSG_ERROR(this packages requires a curses library) AC_MSG_ERROR(this packages requires a curses library)
fi fi
AC_MSG_CHECKING([for curses library])
AC_MSG_RESULT([$curseslib])
AC_MSG_CHECKING([for curses header name])
AC_MSG_RESULT([$CURSESINC])
AC_MSG_CHECKING([for curses compiler flags])
AC_MSG_RESULT([$CURSES_CFLAGS])
dnl CURSESLIBS might have the library path at the beginning. If so, we cut it
dnl off so that we can insert the other curses libraries before the ncurses
dnl library but after the library path (which is later again prepended below).
if test -n "$CURSESLIBDIR"; then
if test "-L$CURSESLIBDIR " = "$(echo $CURSESLIB | cut -c-$((${#CURSESLIBDIR}+3)) )"; then
CURSESLIB="$(echo $CURSESLIB | cut -c$((${#CURSESLIBDIR}+4))-)"
fi
fi
saved_CFLAGS="$CFLAGS"
saved_LDFLAGS="$LDFLAGS"
saved_LIBS="$LIBS"
CFLAGS="$CFLAGS $CURSES_CFLAGS"
if test -n "$CURSESLIBDIR"; then
LDFLAGS="$LDFLAGS -L$CURSESLIBDIR"
fi
LIBS="$CURSESLIB $LIBS"
if test "$USE_NLS" = "yes"; then
AC_MSG_CHECKING([for curses NLS support])
dnl In theory, a single-byte curses works just fine in ISO 8859-* locales.
dnl In practice, however, everybody uses UTF-8 nowadays, so we'd better
dnl check for wide-character support.
dnl For ncurses/ncursesw, CURSES_NLS was already set above.
if test "$curseslib" = "curses"; then
AC_TRY_LINK([
#define _XOPEN_SOURCE 1
#define _XOPEN_SOURCE_EXTENDED 1
#include <curses.h>
], [
cchar_t wc;
setcchar(&wc, L"x", A_NORMAL, 0, 0);
],
[CURSES_NLS="yes"])
fi
AC_MSG_RESULT([$CURSES_NLS])
if test "$CURSES_NLS" = "yes"; then
AC_DEFINE([ENABLE_NLS_IN_CURSES], [1],
[Define if curses-based programs can show translated messages.])
fi
fi
AC_CHECK_HEADERS([panel.h menu.h form.h], [],
[AC_MSG_ERROR([required curses helper header not found])])
AC_CHECK_LIB([panel$NCURSESLIBSUFFIX], [new_panel],
[CURSESLIB="-lpanel$NCURSESLIBSUFFIX $CURSESLIB"],
[AC_MSG_ERROR([panel$NCURSESLIBSUFFIX library not found])])
AC_CHECK_LIB([menu$NCURSESLIBSUFFIX], [new_menu],
[CURSESLIB="-lmenu$NCURSESLIBSUFFIX $CURSESLIB"],
[AC_MSG_ERROR([menu$NCURSESLIBSUFFIX library not found])])
AC_CHECK_LIB([form$NCURSESLIBSUFFIX], [new_form],
[CURSESLIB="-lform$NCURSESLIBSUFFIX $CURSESLIB"],
[AC_MSG_ERROR([form$NCURSESLIBSUFFIX library not found])])
CFLAGS="$saved_CFLAGS"
LDFLAGS="$saved_LDFLAGS"
LIBS="$saved_LIBS"
if test -n "$CURSESLIBDIR"; then
CURSESLIB="-L$CURSESLIBDIR $CURSESLIB"
fi
AC_MSG_CHECKING([for curses linker flags])
AC_MSG_RESULT([$CURSESLIB])
fi fi
AC_SUBST(CURSESINC) AC_SUBST(CURSESINC)

View file

@ -1,4 +1,4 @@
noinst_HEADERS=version.h gettext.h noinst_HEADERS=version.h gettext.h gettext_curses.h
version.h: stamp-vh version.h: stamp-vh
@: @:

View file

@ -1,7 +1,13 @@
#ifndef __MY_GETTEXT_H #ifndef __MY_GETTEXT_H
#define __MY_GETTEXT_H #define __MY_GETTEXT_H
#if ENABLE_NLS #ifdef USES_CURSES
#define ENABLE_NLS_TEST ENABLE_NLS_IN_CURSES
#else
#define ENABLE_NLS_TEST ENABLE_NLS
#endif
#if ENABLE_NLS_TEST
# include <libintl.h> # include <libintl.h>
#else #else
# define gettext(msgid) (msgid) # define gettext(msgid) (msgid)

2
include/gettext_curses.h Normal file
View file

@ -0,0 +1,2 @@
#define USES_CURSES
#include "gettext.h"

View file

@ -18,7 +18,7 @@ XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
# or entity, or to disclaim their copyright. The empty string stands for # or entity, or to disclaim their copyright. The empty string stands for
# the public domain; in this case the translators are expected to disclaim # the public domain; in this case the translators are expected to disclaim
# their copyright. # their copyright.
COPYRIGHT_HOLDER = Free Software Foundation, Inc. COPYRIGHT_HOLDER = The ALSA Team
# This is the email address or URL to which the translators shall report # This is the email address or URL to which the translators shall report
# bugs in the untranslated strings: # bugs in the untranslated strings:

View file

@ -1,3 +1,11 @@
alsamixer/card_select.c
alsamixer/cli.c
alsamixer/device_name.c
alsamixer/die.c
alsamixer/mixer_display.c
alsamixer/mixer_widget.c
alsamixer/proc_files.c
alsamixer/textbox.c
aplay/aplay.c aplay/aplay.c
seq/aconnect/aconnect.c seq/aconnect/aconnect.c
seq/aseqnet/aseqnet.c seq/aseqnet/aseqnet.c

815
po/ja.po

File diff suppressed because it is too large Load diff