mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-12-22 07:36:31 +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@
|
LDADD = @CURSESLIB@
|
||||||
|
|
||||||
bin_PROGRAMS = alsamixer
|
bin_PROGRAMS = alsamixer
|
||||||
|
alsamixer_SOURCES = card_select.c card_select.h \
|
||||||
|
cli.c \
|
||||||
|
colors.c colors.h \
|
||||||
|
device_name.c device_name.h \
|
||||||
|
die.c die.h \
|
||||||
|
mainloop.c mainloop.h \
|
||||||
|
mem.c mem.h \
|
||||||
|
mixer_controls.c mixer_controls.h \
|
||||||
|
mixer_display.c mixer_display.h \
|
||||||
|
mixer_widget.c mixer_widget.h \
|
||||||
|
proc_files.c proc_files.h \
|
||||||
|
textbox.c textbox.h \
|
||||||
|
utils.c utils.h \
|
||||||
|
widget.c widget.h
|
||||||
man_MANS = alsamixer.1
|
man_MANS = alsamixer.1
|
||||||
EXTRA_DIST = alsamixer.1
|
EXTRA_DIST = alsamixer.1
|
||||||
alsamixer_INCLUDES = -I$(top_srcdir)/include
|
alsamixer_INCLUDES = -I$(top_srcdir)/include
|
||||||
|
|
|
@ -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
|
.SH NAME
|
||||||
alsamixer \- soundcard mixer for ALSA soundcard driver, with ncurses interface
|
alsamixer \- soundcard mixer for ALSA soundcard driver, with ncurses interface
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
@ -12,30 +12,26 @@ soundcard drivers. It supports multiple soundcards with multiple devices.
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fI\-h, \-help\fP
|
\fI\-h, \-\-help\fP
|
||||||
Help: show available flags.
|
Help: show available flags.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fI\-c\fP <card number or identification>
|
\fI\-c, \-\-card\fP <card number or identification>
|
||||||
Select the soundcard to use, if you have more than one. Cards are
|
Select the soundcard to use, if you have more than one. Cards are
|
||||||
numbered from 0 (the default).
|
numbered from 0 (the default).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fI\-D\fP <device identification>
|
\fI\-D, \-\-device\fP <device identification>
|
||||||
Select the mixer device to control.
|
Select the mixer device to control.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fI\-g\fP
|
\fI\-V, \-\-view\fP <mode>
|
||||||
Toggle the using of colors.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
\fI\-s\fP
|
|
||||||
Minimize the mixer window.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
\fI\-V\fP <view mode>
|
|
||||||
Select the starting view mode, either \fIplayback\fP, \fIcapture\fP or \fIall\fP.
|
Select the starting view mode, either \fIplayback\fP, \fIcapture\fP or \fIall\fP.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fI\-g, \-\-no\-color\fP
|
||||||
|
Toggle the using of colors.
|
||||||
|
|
||||||
.SH MIXER VIEWS
|
.SH MIXER VIEWS
|
||||||
|
|
||||||
The top-left corner of \fBalsamixer\fP is the are to show some basic
|
The top-left corner of \fBalsamixer\fP is the are to show some basic
|
||||||
|
@ -147,6 +143,13 @@ all UP keys will work like \fIW\fP, and all DOWN keys will work like \fIX\fP.
|
||||||
The number keys from \fI0\fP to \fI9\fP are to change the absolute volume
|
The number keys from \fI0\fP to \fI9\fP are to change the absolute volume
|
||||||
quickly. They correspond to 0 to 90% volume.
|
quickly. They correspond to 0 to 90% volume.
|
||||||
|
|
||||||
|
.SS
|
||||||
|
Selecting the Sound Card
|
||||||
|
|
||||||
|
You can select another sound card by pressing the \fIF6\fP or \fIS\fP keys.
|
||||||
|
This will show a list of available sound cards to choose from,
|
||||||
|
and an entry to enter the mixer device name by hand.
|
||||||
|
|
||||||
.SS
|
.SS
|
||||||
Exiting
|
Exiting
|
||||||
|
|
||||||
|
@ -169,6 +172,7 @@ fault. Plain old \fBxterm\fP seems to be fine.
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
.B alsamixer
|
.B alsamixer
|
||||||
has been written by Tim Janik <timj@gtk.org> and
|
has been written by Tim Janik <timj@gtk.org> and
|
||||||
been further improved by Jaroslav Kysela <perex@perex.cz>.
|
been further improved by Jaroslav Kysela <perex@perex.cz>
|
||||||
|
and Clemens Ladisch <clemens@ladisch.de>.
|
||||||
|
|
||||||
This manual page was provided by Paul Winkler <zarmzarm@erols.com>.
|
This manual page was provided by Paul Winkler <zarmzarm@erols.com>.
|
||||||
|
|
File diff suppressed because it is too large
Load diff
268
alsamixer/card_select.c
Normal file
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.
|
dnl Process this file with autoconf to produce a configure script.
|
||||||
AC_PREREQ(2.59)
|
AC_PREREQ(2.59)
|
||||||
AC_INIT(alsamixer/alsamixer.c)
|
AC_INIT(aplay/aplay.c)
|
||||||
AC_PREFIX_DEFAULT(/usr)
|
AC_PREFIX_DEFAULT(/usr)
|
||||||
AM_INIT_AUTOMAKE(alsa-utils, 1.0.20)
|
AM_INIT_AUTOMAKE(alsa-utils, 1.0.20)
|
||||||
|
|
||||||
|
@ -105,23 +105,32 @@ if test x$alsamixer = xtrue; then
|
||||||
[ --with-curses libname Specify the curses library to use (default=auto)],
|
[ --with-curses libname Specify the curses library to use (default=auto)],
|
||||||
curseslib="$withval",
|
curseslib="$withval",
|
||||||
curseslib="auto")
|
curseslib="auto")
|
||||||
if test "$curseslib" = "ncursesw"; then
|
CURSESLIBDIR=""
|
||||||
|
NCURSESLIBSUFFIX=""
|
||||||
|
CURSES_NLS="no"
|
||||||
|
if test "$curseslib" = "ncursesw" -o \( "$curseslib" = "auto" -a "$USE_NLS" = "yes" \); then
|
||||||
AC_CHECK_PROG([ncursesw5_config], [ncursesw5-config], [yes])
|
AC_CHECK_PROG([ncursesw5_config], [ncursesw5-config], [yes])
|
||||||
if test "$ncursesw5_config" = "yes"; then
|
if test "$ncursesw5_config" = "yes"; then
|
||||||
CURSESINC="<ncurses.h>"
|
CURSESINC="<ncurses.h>"
|
||||||
CURSESLIB=`ncursesw5-config --libs`
|
CURSESLIB=`ncursesw5-config --libs`
|
||||||
|
CURSESLIBDIR=`ncursesw5-config --libdir`
|
||||||
CURSES_CFLAGS=`ncursesw5-config --cflags`
|
CURSES_CFLAGS=`ncursesw5-config --cflags`
|
||||||
curseslib="ncursesw"
|
curseslib="ncursesw"
|
||||||
else
|
else
|
||||||
AC_CHECK_LIB(ncursesw, initscr,
|
AC_CHECK_LIB(ncursesw, initscr,
|
||||||
[ CURSESINC='<ncurses.h>'; CURSESLIB='-lncursesw'; curseslib="ncursesw"])
|
[ CURSESINC='<ncurses.h>'; CURSESLIB='-lncursesw'; curseslib="ncursesw"])
|
||||||
fi
|
fi
|
||||||
|
if test -n "$CURSESINC"; then
|
||||||
|
NCURSESLIBSUFFIX="w"
|
||||||
|
CURSES_NLS="yes"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
if test "$curseslib" = "ncurses" -o "$curseslib" = "auto"; then
|
if test "$curseslib" = "ncurses" -o "$curseslib" = "auto"; then
|
||||||
AC_CHECK_PROG([ncurses5_config], [ncurses5-config], [yes])
|
AC_CHECK_PROG([ncurses5_config], [ncurses5-config], [yes])
|
||||||
if test "$ncurses5_config" = "yes"; then
|
if test "$ncurses5_config" = "yes"; then
|
||||||
CURSESINC="<ncurses.h>"
|
CURSESINC="<ncurses.h>"
|
||||||
CURSESLIB=`ncurses5-config --libs`
|
CURSESLIB=`ncurses5-config --libs`
|
||||||
|
CURSESLIBDIR=`ncurses5-config --libdir`
|
||||||
CURSES_CFLAGS=`ncurses5-config --cflags`
|
CURSES_CFLAGS=`ncurses5-config --cflags`
|
||||||
curseslib="ncurses"
|
curseslib="ncurses"
|
||||||
else
|
else
|
||||||
|
@ -136,6 +145,78 @@ if test x$alsamixer = xtrue; then
|
||||||
if test -z "$CURSESINC"; then
|
if test -z "$CURSESINC"; then
|
||||||
AC_MSG_ERROR(this packages requires a curses library)
|
AC_MSG_ERROR(this packages requires a curses library)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
AC_MSG_CHECKING([for curses library])
|
||||||
|
AC_MSG_RESULT([$curseslib])
|
||||||
|
AC_MSG_CHECKING([for curses header name])
|
||||||
|
AC_MSG_RESULT([$CURSESINC])
|
||||||
|
AC_MSG_CHECKING([for curses compiler flags])
|
||||||
|
AC_MSG_RESULT([$CURSES_CFLAGS])
|
||||||
|
|
||||||
|
dnl CURSESLIBS might have the library path at the beginning. If so, we cut it
|
||||||
|
dnl off so that we can insert the other curses libraries before the ncurses
|
||||||
|
dnl library but after the library path (which is later again prepended below).
|
||||||
|
if test -n "$CURSESLIBDIR"; then
|
||||||
|
if test "-L$CURSESLIBDIR " = "$(echo $CURSESLIB | cut -c-$((${#CURSESLIBDIR}+3)) )"; then
|
||||||
|
CURSESLIB="$(echo $CURSESLIB | cut -c$((${#CURSESLIBDIR}+4))-)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
saved_CFLAGS="$CFLAGS"
|
||||||
|
saved_LDFLAGS="$LDFLAGS"
|
||||||
|
saved_LIBS="$LIBS"
|
||||||
|
CFLAGS="$CFLAGS $CURSES_CFLAGS"
|
||||||
|
if test -n "$CURSESLIBDIR"; then
|
||||||
|
LDFLAGS="$LDFLAGS -L$CURSESLIBDIR"
|
||||||
|
fi
|
||||||
|
LIBS="$CURSESLIB $LIBS"
|
||||||
|
|
||||||
|
if test "$USE_NLS" = "yes"; then
|
||||||
|
AC_MSG_CHECKING([for curses NLS support])
|
||||||
|
dnl In theory, a single-byte curses works just fine in ISO 8859-* locales.
|
||||||
|
dnl In practice, however, everybody uses UTF-8 nowadays, so we'd better
|
||||||
|
dnl check for wide-character support.
|
||||||
|
dnl For ncurses/ncursesw, CURSES_NLS was already set above.
|
||||||
|
if test "$curseslib" = "curses"; then
|
||||||
|
AC_TRY_LINK([
|
||||||
|
#define _XOPEN_SOURCE 1
|
||||||
|
#define _XOPEN_SOURCE_EXTENDED 1
|
||||||
|
#include <curses.h>
|
||||||
|
], [
|
||||||
|
cchar_t wc;
|
||||||
|
setcchar(&wc, L"x", A_NORMAL, 0, 0);
|
||||||
|
],
|
||||||
|
[CURSES_NLS="yes"])
|
||||||
|
fi
|
||||||
|
AC_MSG_RESULT([$CURSES_NLS])
|
||||||
|
if test "$CURSES_NLS" = "yes"; then
|
||||||
|
AC_DEFINE([ENABLE_NLS_IN_CURSES], [1],
|
||||||
|
[Define if curses-based programs can show translated messages.])
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
AC_CHECK_HEADERS([panel.h menu.h form.h], [],
|
||||||
|
[AC_MSG_ERROR([required curses helper header not found])])
|
||||||
|
AC_CHECK_LIB([panel$NCURSESLIBSUFFIX], [new_panel],
|
||||||
|
[CURSESLIB="-lpanel$NCURSESLIBSUFFIX $CURSESLIB"],
|
||||||
|
[AC_MSG_ERROR([panel$NCURSESLIBSUFFIX library not found])])
|
||||||
|
AC_CHECK_LIB([menu$NCURSESLIBSUFFIX], [new_menu],
|
||||||
|
[CURSESLIB="-lmenu$NCURSESLIBSUFFIX $CURSESLIB"],
|
||||||
|
[AC_MSG_ERROR([menu$NCURSESLIBSUFFIX library not found])])
|
||||||
|
AC_CHECK_LIB([form$NCURSESLIBSUFFIX], [new_form],
|
||||||
|
[CURSESLIB="-lform$NCURSESLIBSUFFIX $CURSESLIB"],
|
||||||
|
[AC_MSG_ERROR([form$NCURSESLIBSUFFIX library not found])])
|
||||||
|
|
||||||
|
CFLAGS="$saved_CFLAGS"
|
||||||
|
LDFLAGS="$saved_LDFLAGS"
|
||||||
|
LIBS="$saved_LIBS"
|
||||||
|
|
||||||
|
if test -n "$CURSESLIBDIR"; then
|
||||||
|
CURSESLIB="-L$CURSESLIBDIR $CURSESLIB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
AC_MSG_CHECKING([for curses linker flags])
|
||||||
|
AC_MSG_RESULT([$CURSESLIB])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AC_SUBST(CURSESINC)
|
AC_SUBST(CURSESINC)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
noinst_HEADERS=version.h gettext.h
|
noinst_HEADERS=version.h gettext.h gettext_curses.h
|
||||||
|
|
||||||
version.h: stamp-vh
|
version.h: stamp-vh
|
||||||
@:
|
@:
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
#ifndef __MY_GETTEXT_H
|
#ifndef __MY_GETTEXT_H
|
||||||
#define __MY_GETTEXT_H
|
#define __MY_GETTEXT_H
|
||||||
|
|
||||||
#if ENABLE_NLS
|
#ifdef USES_CURSES
|
||||||
|
#define ENABLE_NLS_TEST ENABLE_NLS_IN_CURSES
|
||||||
|
#else
|
||||||
|
#define ENABLE_NLS_TEST ENABLE_NLS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ENABLE_NLS_TEST
|
||||||
# include <libintl.h>
|
# include <libintl.h>
|
||||||
#else
|
#else
|
||||||
# define gettext(msgid) (msgid)
|
# define gettext(msgid) (msgid)
|
||||||
|
|
2
include/gettext_curses.h
Normal file
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
|
# or entity, or to disclaim their copyright. The empty string stands for
|
||||||
# the public domain; in this case the translators are expected to disclaim
|
# the public domain; in this case the translators are expected to disclaim
|
||||||
# their copyright.
|
# their copyright.
|
||||||
COPYRIGHT_HOLDER = Free Software Foundation, Inc.
|
COPYRIGHT_HOLDER = The ALSA Team
|
||||||
|
|
||||||
# This is the email address or URL to which the translators shall report
|
# This is the email address or URL to which the translators shall report
|
||||||
# bugs in the untranslated strings:
|
# bugs in the untranslated strings:
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
alsamixer/card_select.c
|
||||||
|
alsamixer/cli.c
|
||||||
|
alsamixer/device_name.c
|
||||||
|
alsamixer/die.c
|
||||||
|
alsamixer/mixer_display.c
|
||||||
|
alsamixer/mixer_widget.c
|
||||||
|
alsamixer/proc_files.c
|
||||||
|
alsamixer/textbox.c
|
||||||
aplay/aplay.c
|
aplay/aplay.c
|
||||||
seq/aconnect/aconnect.c
|
seq/aconnect/aconnect.c
|
||||||
seq/aseqnet/aseqnet.c
|
seq/aseqnet/aseqnet.c
|
||||||
|
|
Loading…
Reference in a new issue