mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-12-22 07:56:30 +01:00
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:
parent
bde1d198c1
commit
5b6b5fd14b
38 changed files with 4628 additions and 2747 deletions
|
@ -2,6 +2,20 @@ AM_CFLAGS = @CURSES_CFLAGS@ -DCURSESINC="@CURSESINC@"
|
|||
LDADD = @CURSESLIB@
|
||||
|
||||
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
|
||||
EXTRA_DIST = alsamixer.1
|
||||
alsamixer_INCLUDES = -I$(top_srcdir)/include
|
||||
|
|
|
@ -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>.
|
|
@ -1,4 +1,4 @@
|
|||
.TH ALSAMIXER 1 "15 May 2001"
|
||||
.TH ALSAMIXER 1 "22 May 2009"
|
||||
.SH NAME
|
||||
alsamixer \- soundcard mixer for ALSA soundcard driver, with ncurses interface
|
||||
.SH SYNOPSIS
|
||||
|
@ -12,30 +12,26 @@ soundcard drivers. It supports multiple soundcards with multiple devices.
|
|||
.SH OPTIONS
|
||||
|
||||
.TP
|
||||
\fI\-h, \-help\fP
|
||||
\fI\-h, \-\-help\fP
|
||||
Help: show available flags.
|
||||
|
||||
.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
|
||||
numbered from 0 (the default).
|
||||
|
||||
.TP
|
||||
\fI\-D\fP <device identification>
|
||||
\fI\-D, \-\-device\fP <device identification>
|
||||
Select the mixer device to control.
|
||||
|
||||
.TP
|
||||
\fI\-g\fP
|
||||
Toggle the using of colors.
|
||||
|
||||
.TP
|
||||
\fI\-s\fP
|
||||
Minimize the mixer window.
|
||||
|
||||
.TP
|
||||
\fI\-V\fP <view mode>
|
||||
\fI\-V, \-\-view\fP <mode>
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
Exiting
|
||||
|
||||
|
@ -169,6 +172,7 @@ fault. Plain old \fBxterm\fP seems to be fine.
|
|||
.SH AUTHOR
|
||||
.B alsamixer
|
||||
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>.
|
||||
|
|
File diff suppressed because it is too large
Load diff
268
alsamixer/card_select.c
Normal file
268
alsamixer/card_select.c
Normal 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
7
alsamixer/card_select.h
Normal 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
135
alsamixer/cli.c
Normal 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
103
alsamixer/colors.c
Normal 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
27
alsamixer/colors.h
Normal 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
197
alsamixer/device_name.c
Normal 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
6
alsamixer/device_name.h
Normal 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
39
alsamixer/die.c
Normal 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
7
alsamixer/die.h
Normal 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
135
alsamixer/mainloop.c
Normal 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
10
alsamixer/mainloop.h
Normal 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
68
alsamixer/mem.c
Normal 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
11
alsamixer/mem.h
Normal 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
521
alsamixer/mixer_controls.c
Normal 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();
|
||||
}
|
37
alsamixer/mixer_controls.h
Normal file
37
alsamixer/mixer_controls.h
Normal 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
739
alsamixer/mixer_display.c
Normal 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
10
alsamixer/mixer_display.h
Normal 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
680
alsamixer/mixer_widget.c
Normal 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(¤t_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
36
alsamixer/mixer_widget.h
Normal 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
169
alsamixer/proc_files.c
Normal 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
6
alsamixer/proc_files.h
Normal 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
396
alsamixer/textbox.c
Normal 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
10
alsamixer/textbox.h
Normal 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
111
alsamixer/utils.c
Normal 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
10
alsamixer/utils.h
Normal 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
140
alsamixer/widget.c
Normal 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
33
alsamixer/widget.h
Normal 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
|
85
configure.in
85
configure.in
|
@ -1,6 +1,6 @@
|
|||
dnl Process this file with autoconf to produce a configure script.
|
||||
AC_PREREQ(2.59)
|
||||
AC_INIT(alsamixer/alsamixer.c)
|
||||
AC_INIT(aplay/aplay.c)
|
||||
AC_PREFIX_DEFAULT(/usr)
|
||||
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)],
|
||||
curseslib="$withval",
|
||||
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])
|
||||
if test "$ncursesw5_config" = "yes"; then
|
||||
CURSESINC="<ncurses.h>"
|
||||
CURSESLIB=`ncursesw5-config --libs`
|
||||
CURSESLIBDIR=`ncursesw5-config --libdir`
|
||||
CURSES_CFLAGS=`ncursesw5-config --cflags`
|
||||
curseslib="ncursesw"
|
||||
else
|
||||
AC_CHECK_LIB(ncursesw, initscr,
|
||||
[ CURSESINC='<ncurses.h>'; CURSESLIB='-lncursesw'; curseslib="ncursesw"])
|
||||
fi
|
||||
if test -n "$CURSESINC"; then
|
||||
NCURSESLIBSUFFIX="w"
|
||||
CURSES_NLS="yes"
|
||||
fi
|
||||
fi
|
||||
if test "$curseslib" = "ncurses" -o "$curseslib" = "auto"; then
|
||||
AC_CHECK_PROG([ncurses5_config], [ncurses5-config], [yes])
|
||||
if test "$ncurses5_config" = "yes"; then
|
||||
CURSESINC="<ncurses.h>"
|
||||
CURSESLIB=`ncurses5-config --libs`
|
||||
CURSESLIBDIR=`ncurses5-config --libdir`
|
||||
CURSES_CFLAGS=`ncurses5-config --cflags`
|
||||
curseslib="ncurses"
|
||||
else
|
||||
|
@ -136,6 +145,78 @@ if test x$alsamixer = xtrue; then
|
|||
if test -z "$CURSESINC"; then
|
||||
AC_MSG_ERROR(this packages requires a curses library)
|
||||
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
|
||||
|
||||
AC_SUBST(CURSESINC)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
noinst_HEADERS=version.h gettext.h
|
||||
noinst_HEADERS=version.h gettext.h gettext_curses.h
|
||||
|
||||
version.h: stamp-vh
|
||||
@:
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
#ifndef __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>
|
||||
#else
|
||||
# define gettext(msgid) (msgid)
|
||||
|
|
2
include/gettext_curses.h
Normal file
2
include/gettext_curses.h
Normal file
|
@ -0,0 +1,2 @@
|
|||
#define USES_CURSES
|
||||
#include "gettext.h"
|
|
@ -18,7 +18,7 @@ XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
|
|||
# or entity, or to disclaim their copyright. The empty string stands for
|
||||
# the public domain; in this case the translators are expected to disclaim
|
||||
# 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
|
||||
# bugs in the untranslated strings:
|
||||
|
|
|
@ -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
|
||||
seq/aconnect/aconnect.c
|
||||
seq/aseqnet/aseqnet.c
|
||||
|
|
Loading…
Reference in a new issue