From 5b6b5fd14bd5b0f6b07332657fc09b0a7255dda9 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Mon, 25 May 2009 10:26:22 +0200 Subject: [PATCH] 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 --- alsamixer/Makefile.am | 14 + alsamixer/README | 84 -- alsamixer/alsamixer.1 | 32 +- alsamixer/alsamixer.c | 2412 ------------------------------------ alsamixer/card_select.c | 268 ++++ alsamixer/card_select.h | 7 + alsamixer/cli.c | 135 ++ alsamixer/colors.c | 103 ++ alsamixer/colors.h | 27 + alsamixer/device_name.c | 197 +++ alsamixer/device_name.h | 6 + alsamixer/die.c | 39 + alsamixer/die.h | 7 + alsamixer/mainloop.c | 135 ++ alsamixer/mainloop.h | 10 + alsamixer/mem.c | 68 + alsamixer/mem.h | 11 + alsamixer/mixer_controls.c | 521 ++++++++ alsamixer/mixer_controls.h | 37 + alsamixer/mixer_display.c | 739 +++++++++++ alsamixer/mixer_display.h | 10 + alsamixer/mixer_widget.c | 680 ++++++++++ alsamixer/mixer_widget.h | 36 + alsamixer/proc_files.c | 169 +++ alsamixer/proc_files.h | 6 + alsamixer/textbox.c | 396 ++++++ alsamixer/textbox.h | 10 + alsamixer/utils.c | 111 ++ alsamixer/utils.h | 10 + alsamixer/widget.c | 140 +++ alsamixer/widget.h | 33 + configure.in | 85 +- include/Makefile.am | 2 +- include/gettext.h | 8 +- include/gettext_curses.h | 2 + po/Makevars | 2 +- po/POTFILES.in | 8 + po/ja.po | 815 ++++++++---- 38 files changed, 4628 insertions(+), 2747 deletions(-) delete mode 100644 alsamixer/README delete mode 100644 alsamixer/alsamixer.c create mode 100644 alsamixer/card_select.c create mode 100644 alsamixer/card_select.h create mode 100644 alsamixer/cli.c create mode 100644 alsamixer/colors.c create mode 100644 alsamixer/colors.h create mode 100644 alsamixer/device_name.c create mode 100644 alsamixer/device_name.h create mode 100644 alsamixer/die.c create mode 100644 alsamixer/die.h create mode 100644 alsamixer/mainloop.c create mode 100644 alsamixer/mainloop.h create mode 100644 alsamixer/mem.c create mode 100644 alsamixer/mem.h create mode 100644 alsamixer/mixer_controls.c create mode 100644 alsamixer/mixer_controls.h create mode 100644 alsamixer/mixer_display.c create mode 100644 alsamixer/mixer_display.h create mode 100644 alsamixer/mixer_widget.c create mode 100644 alsamixer/mixer_widget.h create mode 100644 alsamixer/proc_files.c create mode 100644 alsamixer/proc_files.h create mode 100644 alsamixer/textbox.c create mode 100644 alsamixer/textbox.h create mode 100644 alsamixer/utils.c create mode 100644 alsamixer/utils.h create mode 100644 alsamixer/widget.c create mode 100644 alsamixer/widget.h create mode 100644 include/gettext_curses.h diff --git a/alsamixer/Makefile.am b/alsamixer/Makefile.am index 6426193..1de47c6 100644 --- a/alsamixer/Makefile.am +++ b/alsamixer/Makefile.am @@ -2,6 +2,20 @@ AM_CFLAGS = @CURSES_CFLAGS@ -DCURSESINC="@CURSESINC@" LDADD = @CURSESLIB@ bin_PROGRAMS = alsamixer +alsamixer_SOURCES = card_select.c card_select.h \ + cli.c \ + colors.c colors.h \ + device_name.c device_name.h \ + die.c die.h \ + mainloop.c mainloop.h \ + mem.c mem.h \ + mixer_controls.c mixer_controls.h \ + mixer_display.c mixer_display.h \ + mixer_widget.c mixer_widget.h \ + proc_files.c proc_files.h \ + textbox.c textbox.h \ + utils.c utils.h \ + widget.c widget.h man_MANS = alsamixer.1 EXTRA_DIST = alsamixer.1 alsamixer_INCLUDES = -I$(top_srcdir)/include diff --git a/alsamixer/README b/alsamixer/README deleted file mode 100644 index 05c6615..0000000 --- a/alsamixer/README +++ /dev/null @@ -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 and -been furtherly improved by Jaroslav Kysela . -This document was provided by Paul Winkler . diff --git a/alsamixer/alsamixer.1 b/alsamixer/alsamixer.1 index 47d8aed..b16003f 100644 --- a/alsamixer/alsamixer.1 +++ b/alsamixer/alsamixer.1 @@ -1,4 +1,4 @@ -.TH ALSAMIXER 1 "15 May 2001" +.TH ALSAMIXER 1 "22 May 2009" .SH NAME alsamixer \- soundcard mixer for ALSA soundcard driver, with ncurses interface .SH SYNOPSIS @@ -12,30 +12,26 @@ soundcard drivers. It supports multiple soundcards with multiple devices. .SH OPTIONS .TP -\fI\-h, \-help\fP +\fI\-h, \-\-help\fP Help: show available flags. .TP -\fI\-c\fP +\fI\-c, \-\-card\fP Select the soundcard to use, if you have more than one. Cards are numbered from 0 (the default). .TP -\fI\-D\fP +\fI\-D, \-\-device\fP Select the mixer device to control. .TP -\fI\-g\fP -Toggle the using of colors. - -.TP -\fI\-s\fP -Minimize the mixer window. - -.TP -\fI\-V\fP +\fI\-V, \-\-view\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 The top-left corner of \fBalsamixer\fP is the are to show some basic @@ -147,6 +143,13 @@ all UP keys will work like \fIW\fP, and all DOWN keys will work like \fIX\fP. The number keys from \fI0\fP to \fI9\fP are to change the absolute volume quickly. They correspond to 0 to 90% volume. +.SS +Selecting the Sound Card + +You can select another sound card by pressing the \fIF6\fP or \fIS\fP keys. +This will show a list of available sound cards to choose from, +and an entry to enter the mixer device name by hand. + .SS Exiting @@ -169,6 +172,7 @@ fault. Plain old \fBxterm\fP seems to be fine. .SH AUTHOR .B alsamixer has been written by Tim Janik and -been further improved by Jaroslav Kysela . +been further improved by Jaroslav Kysela +and Clemens Ladisch . This manual page was provided by Paul Winkler . diff --git a/alsamixer/alsamixer.c b/alsamixer/alsamixer.c deleted file mode 100644 index c65c22d..0000000 --- a/alsamixer/alsamixer.c +++ /dev/null @@ -1,2412 +0,0 @@ -/* AlsaMixer - Commandline mixer for the ALSA project Copyright (C) 1998, - * 1999 Tim Janik and Jaroslav Kysela - * - * 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 Library General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - * - * - * ChangeLog: - * - * Wed Feb 14 13:08:17 CET 2001 Jaroslav Kysela - * - * * ported to the latest mixer 0.9.x API (function based) - * - * Fri Jun 23 14:10:00 MEST 2000 Jaroslav Kysela - * - * * ported to new mixer 0.9.x API (simple control) - * * improved error handling (mixer_abort) - * - * Thu Mar 9 22:54:16 MET 2000 Takashi iwai - * - * * a group is split into front, rear, center and woofer elements. - * - * Mon Jan 3 23:33:42 MET 2000 Jaroslav Kysela - * - * * version 1.00 - * - * * ported to new mixer API (scontrol control) - * - * Sun Feb 21 19:55:01 1999 Tim Janik - * - * * bumped version to 0.10. - * - * * added scrollable text views. - * we now feature an F1 Help screen and an F2 /proc info screen. - * the help screen does still require lots of work though. - * - * * keys are evaluated view specific now. - * - * * we feature meta-keys now, e.g. M-Tab as back-tab. - * - * * if we are already in channel view and the user still hits Return, - * we do a refresh nonetheless, since 'r'/'R' got removed as a redraw - * key (reserved for capture volumes). 'l'/'L' is still preserved though, - * and actually needs to be to e.g. get around the xterm bold-artefacts. - * - * * support terminals that can't write into lower right corner. - * - * * undocumented '-s' option that will keep the screen to its - * minimum size, usefull for debugging only. - * - * Sun Feb 21 02:23:52 1999 Tim Janik - * - * * don't abort if snd_mixer_* functions failed due to EINTR, - * we simply retry on the next cycle. hopefully asoundlib preserves - * errno states correctly (Jaroslav can you asure that?). - * - * * feature WINCH correctly, so we make a complete relayout on - * screen resizes. don't abort on too-small screen sizes anymore, - * but simply beep. - * - * * redid the layout algorithm to fix some bugs and to preserve - * space for a flag indication line. the channels are - * nicer spread horizontally now (i.e. we also pad on the left and - * right screen bounds now). - * - * * various other minor fixes. - * - * * indicate whether ExactMode is active or not. - * - * * fixed coding style to follow the GNU coding conventions. - * - * * reverted capture volume changes since they broke ExactMode display. - * - * * composed ChangeLog entries. - * - * 1998/11/04 19:43:45 perex - * - * * Stereo capture source and route selection... - * provided by Carl van Schaik . - * - * 1998/09/20 08:05:24 perex - * - * * Fixed -m option... - * - * 1998/10/29 22:50:10 - * - * * initial checkin of alsamixer.c, written by Tim Janik, modified by - * Jaroslav Kysela to feature asoundlib.h instead of plain ioctl()s and - * automated updates after select() (i always missed that with OSS!). - */ - -#define _GNU_SOURCE -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#ifndef CURSESINC -#include -#else -#include CURSESINC -#endif -#include - -#include -#include "aconfig.h" - -/* example compilation commandline: - * clear; gcc -Wall -pipe -O2 alsamixer.c -o alsamixer -lasound -lncurses - */ - -/* --- defines --- */ -#define PRGNAME "alsamixer" -#define PRGNAME_UPPER "AlsaMixer" -#define CHECK_ABORT(e,s,n) ({ if ((n) != -EINTR) mixer_abort ((e), (s), (n)); }) -#define GETCH_BLOCK(w) ({ timeout ((w) ? -1 : 0); }) - -#undef MAX -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) -#undef MIN -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#undef ABS -#define ABS(a) (((a) < 0) ? -(a) : (a)) -#undef CLAMP -#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) - -#define MIXER_MIN_X (18) /* abs minimum: 18 */ -#define MIXER_TEXT_Y (10) -#define MIXER_CBAR_STD_HGT (10) -#define MIXER_MIN_Y (MIXER_TEXT_Y + 6) /* abs minimum: 16 */ - -#define MIXER_BLACK (COLOR_BLACK) -#define MIXER_DARK_RED (COLOR_RED) -#define MIXER_RED (COLOR_RED | A_BOLD) -#define MIXER_GREEN (COLOR_GREEN | A_BOLD) -#define MIXER_ORANGE (COLOR_YELLOW) -#define MIXER_YELLOW (COLOR_YELLOW | A_BOLD) -#define MIXER_MARIN (COLOR_BLUE) -#define MIXER_BLUE (COLOR_BLUE | A_BOLD) -#define MIXER_MAGENTA (COLOR_MAGENTA) -#define MIXER_DARK_CYAN (COLOR_CYAN) -#define MIXER_CYAN (COLOR_CYAN | A_BOLD) -#define MIXER_GREY (COLOR_WHITE) -#define MIXER_GRAY (MIXER_GREY) -#define MIXER_WHITE (COLOR_WHITE | A_BOLD) - - -/* --- views --- */ -enum { - VIEW_CHANNELS, - VIEW_PLAYBACK, - VIEW_CAPTURE, - VIEW_HELP, - VIEW_PROCINFO -}; - - -/* --- variables --- */ -static WINDOW *mixer_window = NULL; -static int mixer_needs_resize = 0; -static int mixer_minimize = 0; -static int mixer_no_lrcorner = 0; -static int mixer_view = VIEW_PLAYBACK; -static int mixer_view_saved = VIEW_PLAYBACK; -static int mixer_max_x = 0; -static int mixer_max_y = 0; -static int mixer_ofs_x = 0; -static float mixer_extra_space = 0; -static int mixer_cbar_height = 0; -static int mixer_text_y = MIXER_TEXT_Y; - -static char card_id[64] = "default"; -static snd_mixer_t *mixer_handle; -static char mixer_card_name[128]; -static char mixer_device_name[128]; -static int mixer_level = 0; -static struct snd_mixer_selem_regopt mixer_options; - -/* mixer bar channel : left or right */ -#define MIXER_CHN_LEFT 0 -#define MIXER_CHN_RIGHT 1 -/* mask for toggle mute and capture */ -#define MIXER_MASK_LEFT (1 << 0) -#define MIXER_MASK_RIGHT (1 << 1) -#define MIXER_MASK_STEREO (MIXER_MASK_LEFT|MIXER_MASK_RIGHT) - -/* mixer split types */ -enum { - MIXER_ELEM_FRONT, MIXER_ELEM_REAR, - MIXER_ELEM_CENTER, MIXER_ELEM_WOOFER, - MIXER_ELEM_SIDE, - MIXER_ELEM_CAPTURE, - MIXER_ELEM_ENUM, MIXER_ELEM_CAPTURE_ENUM, - MIXER_ELEM_END -}; - -#define MIXER_ELEM_TYPE_MASK 0xff -#define MIXER_ELEM_CAPTURE_SWITCH 0x100 /* bit */ -#define MIXER_ELEM_MUTE_SWITCH 0x200 /* bit */ -#define MIXER_ELEM_CAPTURE_SUFFIX 0x400 -#define MIXER_ELEM_HAS_VOLUME 0x800 - -/* left and right channels for each type */ -static const snd_mixer_selem_channel_id_t mixer_elem_chn[][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 }, - { SND_MIXER_SCHN_FRONT_LEFT, SND_MIXER_SCHN_FRONT_RIGHT }, -}; - -static void *mixer_sid = NULL; -static int mixer_n_selems = 0; -static int mixer_changed_state = 1; - -/* split scontrols */ -static int mixer_n_elems = 0; -static int mixer_n_view_elems = 0; -static int mixer_n_vis_elems = 0; -static int mixer_first_vis_elem = 0; -static int mixer_focus_elem = 0; -static int mixer_have_old_focus = 0; -static int *mixer_grpidx; -static int *mixer_type; - -static int mixer_volume_delta[2]; /* left/right volume delta in % */ -static int mixer_volume_absolute = -1; /* absolute volume settings in % */ -static int mixer_balance_volumes = 0; /* boolean */ -static unsigned mixer_toggle_mute = 0; /* left/right mask */ -static unsigned mixer_toggle_capture = 0; /* left/right mask */ - -static int mixer_hscroll_delta = 0; -static int mixer_vscroll_delta = 0; - - -/* --- text --- */ -static int mixer_procinfo_xoffs = 0; -static int mixer_procinfo_yoffs = 0; -static int mixer_help_xoffs = 0; -static int mixer_help_yoffs = 0; -static char *mixer_help_text = -( - " Esc exit alsamixer\n" - " F1 ? show Help screen\n" - " F2 / show /proc info screen\n" - " F3 show Playback controls only\n" - " F4 show Capture controls only\n" - " F5 show all controls\n" - " Tab toggle view mode\n" - " Return return to main screen\n" - " Space toggle Capture facility\n" - " m M toggle mute on both channels\n" - " < > toggle mute on left/right channel\n" - " Up increase left and right volume\n" - " Down decrease left and right volume\n" - " Right move (scroll) to the right next channel\n" - " Left move (scroll) to the left next channel\n" - "\n" - "Alsamixer has been written and is Copyrighted in 1998, 1999 by\n" - "Tim Janik and Jaroslav Kysela .\n" - ); - - -/* --- draw contexts --- */ -enum { - DC_DEFAULT, - DC_BACK, - DC_TEXT, - DC_PROMPT, - DC_CBAR_FRAME, - DC_CBAR_MUTE, - DC_CBAR_NOMUTE, - DC_CBAR_CAPTURE, - DC_CBAR_NOCAPTURE, - DC_CBAR_EMPTY, - DC_CBAR_LABEL, - DC_CBAR_FOCUS_LABEL, - DC_FOCUS, - DC_ANY_1, - DC_ANY_2, - DC_ANY_3, - DC_ANY_4, - DC_LAST -}; - -static int dc_fg[DC_LAST] = { 0 }; -static int dc_attrib[DC_LAST] = { 0 }; -static int dc_char[DC_LAST] = { 0 }; -static int mixer_do_color = 1; - -static void -mixer_init_dc (int c, - int n, - int f, - int b, - int a) -{ - dc_fg[n] = f; - dc_attrib[n] = a; - dc_char[n] = c; - if (n > 0) - init_pair (n, dc_fg[n] & 0xf, b & 0x0f); -} - -static int -mixer_dc (int n) -{ - if (mixer_do_color) - attrset (COLOR_PAIR (n) | (dc_fg[n] & 0xfffffff0)); - else - attrset (dc_attrib[n]); - - return dc_char[n]; -} - -static void -mixer_init_draw_contexts (void) -{ - start_color (); - - mixer_init_dc ('.', DC_BACK, MIXER_WHITE, MIXER_BLACK, A_NORMAL); - mixer_init_dc ('.', DC_TEXT, MIXER_YELLOW, MIXER_BLACK, A_BOLD); - mixer_init_dc ('.', DC_PROMPT, MIXER_DARK_CYAN, MIXER_BLACK, A_NORMAL); - mixer_init_dc ('.', DC_CBAR_FRAME, MIXER_CYAN, MIXER_BLACK, A_BOLD); - mixer_init_dc ('M', DC_CBAR_MUTE, MIXER_DARK_CYAN, MIXER_BLACK, A_NORMAL); - mixer_init_dc ('O', DC_CBAR_NOMUTE, MIXER_WHITE, MIXER_GREEN, A_BOLD); - mixer_init_dc ('x', DC_CBAR_CAPTURE, MIXER_DARK_RED, MIXER_BLACK, A_BOLD); - mixer_init_dc ('-', DC_CBAR_NOCAPTURE, MIXER_GRAY, MIXER_BLACK, A_NORMAL); - mixer_init_dc (' ', DC_CBAR_EMPTY, MIXER_GRAY, MIXER_BLACK, A_DIM); - mixer_init_dc ('.', DC_CBAR_LABEL, MIXER_WHITE, MIXER_BLUE, A_REVERSE | A_BOLD); - mixer_init_dc ('.', DC_CBAR_FOCUS_LABEL, MIXER_RED, MIXER_BLUE, A_REVERSE | A_BOLD); - mixer_init_dc ('.', DC_FOCUS, MIXER_RED, MIXER_BLACK, A_BOLD); - mixer_init_dc (ACS_CKBOARD, DC_ANY_1, MIXER_WHITE, MIXER_WHITE, A_BOLD); - mixer_init_dc (ACS_CKBOARD, DC_ANY_2, MIXER_GREEN, MIXER_GREEN, A_BOLD); - mixer_init_dc (ACS_CKBOARD, DC_ANY_3, MIXER_RED, MIXER_RED, A_BOLD); - mixer_init_dc ('.', DC_ANY_4, MIXER_WHITE, MIXER_BLUE, A_BOLD); -} - -#define DC_FRAME (DC_PROMPT) - - -/* --- error types --- */ -typedef enum -{ - ERR_NONE, - ERR_OPEN, - ERR_FCN, - ERR_SIGNAL, - ERR_WINSIZE, -} ErrType; - - -/* --- prototypes --- */ -static void -mixer_abort (ErrType error, - const char *err_string, - int xerrno) - __attribute__ -((noreturn)); - - -/* --- functions --- */ -static void -mixer_clear (int full_redraw) -{ - int x, y; - int f = full_redraw ? 0 : 1; - - mixer_dc (DC_BACK); - - if (full_redraw) - clearok (mixer_window, TRUE); - - /* buggy ncurses doesn't really write spaces with the specified - * color into the screen on clear () or erase () - */ - for (x = f; x < mixer_max_x - f; x++) - for (y = f; y < mixer_max_y - f; y++) - mvaddch (y, x, ' '); -} - -static void -mixer_abort (ErrType error, - const char *err_string, - int xerrno) -{ - if (mixer_window) - { - mixer_clear (TRUE); - refresh (); - keypad (mixer_window, FALSE); - leaveok (mixer_window, FALSE); - endwin (); - mixer_window = NULL; - } - printf ("\n"); - - switch (error) - { - case ERR_OPEN: - fprintf (stderr, - PRGNAME ": function %s failed for %s: %s\n", - err_string, - card_id, - snd_strerror (xerrno)); - break; - case ERR_FCN: - fprintf (stderr, - PRGNAME ": function %s failed: %s\n", - err_string, - snd_strerror (xerrno)); - break; - case ERR_SIGNAL: - fprintf (stderr, - PRGNAME ": aborting due to signal `%s'\n", - err_string); - break; - case ERR_WINSIZE: - fprintf (stderr, - PRGNAME ": screen size too small (%dx%d)\n", - mixer_max_x, - mixer_max_y); - break; - default: - break; - } - - exit (error); -} - -static int -mixer_cbar_get_pos (int elem_index, - int *x_p, - int *y_p) -{ - int x; - int y; - - if (elem_index < mixer_first_vis_elem || - elem_index - mixer_first_vis_elem >= mixer_n_vis_elems) - return FALSE; - - elem_index -= mixer_first_vis_elem; - - x = mixer_ofs_x; - x += (3 + 2 + 3 + 1) * elem_index + mixer_extra_space * (elem_index + 1); - - if (mixer_text_y + MIXER_CBAR_STD_HGT < mixer_max_y) - y = (mixer_text_y + mixer_cbar_height) / 2 - 1 + mixer_max_y / 2; - else - y = mixer_text_y - 1 + mixer_cbar_height; - if (y >= mixer_max_y - 1) - y = mixer_max_y - 2; - if (x_p) - *x_p = x; - if (y_p) - *y_p = y; - - return TRUE; -} - -static int -mixer_conv(int val, int omin, int omax, int nmin, int nmax) -{ - float orange = omax - omin, nrange = nmax - nmin; - - if (orange == 0) - return 0; - return ((nrange * (val - omin)) + (orange / 2)) / orange + nmin; -} - -static int -mixer_calc_volume(snd_mixer_elem_t *elem, - int vol, int type, - snd_mixer_selem_channel_id_t chn) -{ - int vol1; - long v; - long min, max; - if (type != MIXER_ELEM_CAPTURE) - snd_mixer_selem_get_playback_volume_range(elem, &min, &max); - else - snd_mixer_selem_get_capture_volume_range(elem, &min, &max); - vol1 = (vol < 0) ? -vol : vol; - if (vol1 > 0) { - if (vol1 > 100) - vol1 = max; - else - vol1 = mixer_conv(vol1, 0, 100, min, max); - /* Note: we have delta in vol1 and we need to map our */ - /* delta value to hardware range */ - vol1 -= min; - if (vol1 <= 0) - vol1 = 1; - if (vol < 0) - vol1 = -vol1; - } - if (type != MIXER_ELEM_CAPTURE) - snd_mixer_selem_get_playback_volume(elem, chn, &v); - else - snd_mixer_selem_get_capture_volume(elem, chn, &v); - vol1 += v; - return CLAMP(vol1, min, max); -} - -static int -mixer_convert_volume(snd_mixer_elem_t *elem, - int vol, int type) -{ - long min, max; - if (type != MIXER_ELEM_CAPTURE) - snd_mixer_selem_get_playback_volume_range(elem, &min, &max); - else - snd_mixer_selem_get_capture_volume_range(elem, &min, &max); - return mixer_conv(vol, 0, 100, min, max); -} - -/* update enum list */ -static void update_enum_list(snd_mixer_elem_t *elem, int chn, int delta) -{ - unsigned int eidx; - if (snd_mixer_selem_get_enum_item(elem, chn, &eidx) < 0) - return; - if (delta < 0) { - if (eidx == 0) - return; - eidx--; - } else { - int items = snd_mixer_selem_get_enum_items(elem); - if (items < 0) - return; - eidx++; - if (eidx >= items) - return; - } - snd_mixer_selem_set_enum_item(elem, chn, eidx); -} - -/* set new channel values - */ -static void -mixer_write_cbar (int elem_index) -{ - snd_mixer_elem_t *elem; - int vleft, vright, vbalance; - int type; - snd_mixer_selem_id_t *sid; - snd_mixer_selem_channel_id_t chn_left, chn_right, chn; - int sw; - - if (mixer_sid == NULL) - return; - - sid = (snd_mixer_selem_id_t *)(((char *)mixer_sid) + snd_mixer_selem_id_sizeof() * mixer_grpidx[elem_index]); - elem = snd_mixer_find_selem(mixer_handle, sid); - if (elem == NULL) - CHECK_ABORT (ERR_FCN, "snd_mixer_find_selem()", -EINVAL); - type = mixer_type[elem_index] & MIXER_ELEM_TYPE_MASK; - chn_left = mixer_elem_chn[type][MIXER_CHN_LEFT]; - chn_right = mixer_elem_chn[type][MIXER_CHN_RIGHT]; - if (chn_right != SND_MIXER_SCHN_UNKNOWN) { - if (type != MIXER_ELEM_CAPTURE) { - if (!snd_mixer_selem_has_playback_channel(elem, chn_right)) - chn_right = SND_MIXER_SCHN_UNKNOWN; - } else { - if (!snd_mixer_selem_has_capture_channel(elem, chn_right)) - chn_right = SND_MIXER_SCHN_UNKNOWN; - } - } - - /* volume - */ - if ((mixer_volume_delta[MIXER_CHN_LEFT] || - mixer_volume_delta[MIXER_CHN_RIGHT] || - mixer_volume_absolute != -1 || - mixer_balance_volumes) && - (mixer_type[elem_index] & MIXER_ELEM_HAS_VOLUME)) { - int mono; - int joined; - mono = (chn_right == SND_MIXER_SCHN_UNKNOWN); - if (type != MIXER_ELEM_CAPTURE) - joined = snd_mixer_selem_has_playback_volume_joined(elem); - else - joined = snd_mixer_selem_has_capture_volume_joined(elem); - mono |= joined; - if (mixer_volume_absolute != -1) { - vbalance = vright = vleft = mixer_convert_volume(elem, mixer_volume_absolute, type); - } else { - if (mono && !mixer_volume_delta[MIXER_CHN_LEFT]) - mixer_volume_delta[MIXER_CHN_LEFT] = mixer_volume_delta[MIXER_CHN_RIGHT]; - vleft = mixer_calc_volume(elem, mixer_volume_delta[MIXER_CHN_LEFT], type, chn_left); - vbalance = vleft; - if (! mono) { - vright = mixer_calc_volume(elem, mixer_volume_delta[MIXER_CHN_RIGHT], type, chn_right); - vbalance += vright; - vbalance /= 2; - } else { - vright = vleft; - } - } - - if (joined) { - for (chn = 0; chn < SND_MIXER_SCHN_LAST; chn++) - if (type != MIXER_ELEM_CAPTURE) { - if (snd_mixer_selem_has_playback_channel(elem, chn)) - snd_mixer_selem_set_playback_volume(elem, chn, vleft); - } else { - if (snd_mixer_selem_has_capture_channel(elem, chn)) - snd_mixer_selem_set_capture_volume(elem, chn, vleft); - } - } else { - if (mixer_balance_volumes) - vleft = vright = vbalance; - if (type != MIXER_ELEM_CAPTURE) { - if (snd_mixer_selem_has_playback_volume(elem) && - snd_mixer_selem_has_playback_channel(elem, chn_left)) - snd_mixer_selem_set_playback_volume(elem, chn_left, vleft); - } else { - if (snd_mixer_selem_has_capture_volume(elem) && - snd_mixer_selem_has_capture_channel(elem, chn_left)) - snd_mixer_selem_set_capture_volume(elem, chn_left, vleft); - } - if (! mono) { - if (type != MIXER_ELEM_CAPTURE) { - if (snd_mixer_selem_has_playback_volume(elem) && - snd_mixer_selem_has_playback_channel(elem, chn_right)) - snd_mixer_selem_set_playback_volume(elem, chn_right, vright); - } else { - if (snd_mixer_selem_has_capture_volume(elem) && - snd_mixer_selem_has_capture_channel(elem, chn_right)) - snd_mixer_selem_set_capture_volume(elem, chn_right, vright); - } - } - } - } - - /* mute - */ - if (mixer_type[elem_index] & MIXER_ELEM_MUTE_SWITCH) { - if (mixer_toggle_mute) { - if (snd_mixer_selem_has_playback_switch_joined(elem)) { - snd_mixer_selem_get_playback_switch(elem, chn_left, &sw); - snd_mixer_selem_set_playback_switch_all(elem, !sw); - } else { - if (mixer_toggle_mute & MIXER_MASK_LEFT) { - snd_mixer_selem_get_playback_switch(elem, chn_left, &sw); - snd_mixer_selem_set_playback_switch(elem, chn_left, !sw); - } - if (chn_right != SND_MIXER_SCHN_UNKNOWN && - (mixer_toggle_mute & MIXER_MASK_RIGHT)) { - snd_mixer_selem_get_playback_switch(elem, chn_right, &sw); - snd_mixer_selem_set_playback_switch(elem, chn_right, !sw); - } - } - } - } - mixer_toggle_mute = 0; - - /* capture - */ - if (mixer_type[elem_index] & MIXER_ELEM_CAPTURE_SWITCH) { - if (mixer_toggle_capture && snd_mixer_selem_has_capture_switch(elem)) { - if (snd_mixer_selem_has_capture_switch_joined(elem)) { - snd_mixer_selem_get_capture_switch(elem, chn_left, &sw); - snd_mixer_selem_set_capture_switch_all(elem, !sw); - } else { - if ((mixer_toggle_capture & MIXER_MASK_LEFT) && - snd_mixer_selem_has_capture_channel(elem, chn_left)) { - snd_mixer_selem_get_capture_switch(elem, chn_left, &sw); - snd_mixer_selem_set_capture_switch(elem, chn_left, !sw); - } - if (chn_right != SND_MIXER_SCHN_UNKNOWN && - snd_mixer_selem_has_capture_channel(elem, chn_right) && - (mixer_toggle_capture & MIXER_MASK_RIGHT)) { - snd_mixer_selem_get_capture_switch(elem, chn_right, &sw); - snd_mixer_selem_set_capture_switch(elem, chn_right, !sw); - } - } - } - } - mixer_toggle_capture = 0; - - /* enum list - */ - if (type == MIXER_ELEM_ENUM || type == MIXER_ELEM_CAPTURE_ENUM) { - if (mixer_volume_delta[MIXER_CHN_LEFT]) - update_enum_list(elem, MIXER_CHN_LEFT, mixer_volume_delta[MIXER_CHN_LEFT]); - if (mixer_volume_delta[MIXER_CHN_RIGHT]) - update_enum_list(elem, MIXER_CHN_RIGHT, mixer_volume_delta[MIXER_CHN_RIGHT]); - } - - mixer_volume_delta[MIXER_CHN_LEFT] = mixer_volume_delta[MIXER_CHN_RIGHT] = 0; - mixer_volume_absolute = -1; - mixer_balance_volumes = 0; -} - - -static void draw_blank(int x, int y, int lines) -{ - int i; - - mixer_dc (DC_TEXT); - for (i = 0; i < lines; i++) - mvaddstr (y - i, x, " "); -} - -/* show the current view mode */ -static void display_view_info(void) -{ - mixer_dc (DC_PROMPT); - mvaddstr (3, 2, "View: Playback Capture All "); - mixer_dc (DC_TEXT); - switch (mixer_view) { - case VIEW_PLAYBACK: - mvaddstr (3, 8, "[Playback]"); - break; - case VIEW_CAPTURE: - mvaddstr (3, 18, "[Capture]"); - break; - default: - mvaddstr (3, 27, "[All]"); - break; - } -} - -/* show the information of the focused item */ -static void display_item_info(int elem_index, snd_mixer_selem_id_t *sid, char *extra_info) -{ - char string[64], idxstr[10]; - int idx; - int i, xlen = mixer_max_x - 8; - if (xlen > sizeof(string) - 1) - xlen = sizeof(string) - 1; - mixer_dc (DC_PROMPT); - mvaddstr (4, 2, "Item: "); - mixer_dc (DC_TEXT); - idx = snd_mixer_selem_id_get_index(sid); - if (idx > 0) - snprintf(idxstr, sizeof(idxstr), " %i", snd_mixer_selem_id_get_index(sid)); - snprintf(string, sizeof(string), "%s%s%s%s", - snd_mixer_selem_id_get_name(sid), - (mixer_type[elem_index] & MIXER_ELEM_CAPTURE_SUFFIX) ? " Capture" : "", - idx > 0 ? idxstr : "", - extra_info); - for (i = strlen(string); i < sizeof(string) - 1; i++) - string[i] = ' '; - string[xlen] = '\0'; - addstr(string); -} - -/* show the bar item name */ -static void display_item_name(int x, int y, int elem_index, snd_mixer_selem_id_t *sid) -{ - const char *suffix; - char string1[9], string[9]; - int i; - - mixer_dc (elem_index == mixer_focus_elem ? DC_CBAR_FOCUS_LABEL : DC_CBAR_LABEL); - if (mixer_type[elem_index] & MIXER_ELEM_CAPTURE_SUFFIX) - suffix = " Capture"; - else - suffix = ""; - if (snd_mixer_selem_id_get_index(sid) > 0) - snprintf(string1, sizeof(string1), "%s%s %d", snd_mixer_selem_id_get_name(sid), - suffix, snd_mixer_selem_id_get_index(sid)); - else - snprintf(string1, sizeof(string1), "%s%s", snd_mixer_selem_id_get_name(sid), suffix); - string[8] = 0; - for (i = 0; i < 8; i++) - string[i] = ' '; - memcpy(string + (8 - strlen (string1)) / 2, string1, strlen(string1)); - mvaddstr (y, x, string); -} - -static void display_enum_list(snd_mixer_elem_t *elem, int y, int x) -{ - int cury, ch, err; - - draw_blank(x, y, mixer_cbar_height + (mixer_view == VIEW_PLAYBACK ? 5 : 6)); - - cury = y - 4; - for (ch = 0; ch < 2; ch++) { - unsigned int eidx, ofs; - char tmp[9]; - err = snd_mixer_selem_get_enum_item(elem, ch, &eidx); - if (err < 0) - break; - if (snd_mixer_selem_get_enum_item_name(elem, eidx, sizeof(tmp) - 1, tmp) < 0) - break; - tmp[8] = 0; - ofs = (8 - strlen(tmp)) / 2; - mvaddstr(cury, x + ofs, tmp); - cury += 2; - } -} - -static void draw_volume_bar(int x, int y, int elem_index, long vleft, long vright) -{ - int i, dc; - - mixer_dc (DC_CBAR_FRAME); - if (mixer_type[elem_index] & MIXER_ELEM_MUTE_SWITCH) { - mvaddch (y, x + 2, ACS_LTEE); - mvaddch (y, x + 5, ACS_RTEE); - } else { - mvaddch (y, x + 2, ACS_LLCORNER); - mvaddch (y, x + 3, ACS_HLINE); - mvaddch (y, x + 4, ACS_HLINE); - mvaddch (y, x + 5, ACS_LRCORNER); - } - y--; - for (i = 0; i < mixer_cbar_height; i++) - { - mvaddstr (y - i, x, " "); - mvaddch (y - i, x + 2, ACS_VLINE); - mvaddch (y - i, x + 5, ACS_VLINE); - } - for (i = 0; i < mixer_cbar_height; i++) - { - if (i + 1 >= 0.8 * mixer_cbar_height) - dc = DC_ANY_3; - else if (i + 1 >= 0.4 * mixer_cbar_height) - dc = DC_ANY_2; - else - dc = DC_ANY_1; - mvaddch (y, x + 3, mixer_dc (vleft > i * 100 / mixer_cbar_height ? dc : DC_CBAR_EMPTY)); - mvaddch (y, x + 4, mixer_dc (vright > i * 100 / mixer_cbar_height ? dc : DC_CBAR_EMPTY)); - y--; - } - - mixer_dc (DC_CBAR_FRAME); - mvaddstr (y, x, " "); - mvaddch (y, x + 2, ACS_ULCORNER); - mvaddch (y, x + 3, ACS_HLINE); - mvaddch (y, x + 4, ACS_HLINE); - mvaddch (y, x + 5, ACS_URCORNER); -} - -static void draw_playback_switch(int x, int y, int elem_index, int swl, int swr) -{ - int dc; - - mixer_dc (DC_CBAR_FRAME); - mvaddch (y, x + 2, ACS_LLCORNER); - mvaddch (y, x + 3, ACS_HLINE); - mvaddch (y, x + 4, ACS_HLINE); - mvaddch (y, x + 5, ACS_LRCORNER); - mvaddstr (y - 1, x, " "); - mvaddch (y - 1, x + 2, ACS_VLINE); - mvaddch (y - 1, x + 5, ACS_VLINE); - mvaddstr (y - 2, x, " "); - mvaddch (y - 2, x + 2, ACS_ULCORNER); - mvaddch (y - 2, x + 3, ACS_HLINE); - mvaddch (y - 2, x + 4, ACS_HLINE); - mvaddch (y - 2, x + 5, ACS_URCORNER); - dc = swl ? DC_CBAR_NOMUTE : DC_CBAR_MUTE; - mvaddch (y - 1, x + 3, mixer_dc (dc)); - dc = swr ? DC_CBAR_NOMUTE : DC_CBAR_MUTE; - mvaddch (y - 1, x + 4, mixer_dc (dc)); -} - -static void draw_capture_switch(int x, int y, int elem_index, int swl, int swr) -{ - int i; - - if (swl || swr) { - mixer_dc (DC_CBAR_CAPTURE); - mvaddstr (y, x + 1, "CAPTUR"); - } else { - for (i = 0; i < 6; i++) - mvaddch(y, x + i + 1, mixer_dc(DC_CBAR_NOCAPTURE)); - } - mixer_dc (DC_CBAR_CAPTURE); - mvaddch (y - 1, x + 1, swl ? 'L' : ' '); - mvaddch (y - 1, x + 6, swr ? 'R' : ' '); -} - -#ifndef SND_CTL_TLV_DB_GAIN_MUTE -#define SND_CTL_TLV_DB_GAIN_MUTE -9999999 -#endif - -static void dB_value(char *s, long val) -{ - if (val <= SND_CTL_TLV_DB_GAIN_MUTE) - strcpy(s, "mute"); - else - snprintf(s, 10, "%3.2f", (float)val / 100); -} - -static void -mixer_update_cbar (int elem_index) -{ - snd_mixer_elem_t *elem; - long vleft, vright; - int type; - snd_mixer_selem_id_t *sid; - snd_mixer_selem_channel_id_t chn_left, chn_right; - int x, y; - int swl, swr; - char * extra_info; - - /* set new scontrol indices and read info - */ - if (mixer_sid == NULL) - return; - - sid = (snd_mixer_selem_id_t *)(((char *)mixer_sid) + snd_mixer_selem_id_sizeof() * mixer_grpidx[elem_index]); - elem = snd_mixer_find_selem(mixer_handle, sid); - if (elem == NULL) - CHECK_ABORT (ERR_FCN, "snd_mixer_find_selem()", -EINVAL); - - type = mixer_type[elem_index] & MIXER_ELEM_TYPE_MASK; - chn_left = mixer_elem_chn[type][MIXER_CHN_LEFT]; - chn_right = mixer_elem_chn[type][MIXER_CHN_RIGHT]; - if (chn_right != SND_MIXER_SCHN_UNKNOWN) { - if (type != MIXER_ELEM_CAPTURE) { - if (!snd_mixer_selem_has_playback_channel(elem, chn_right)) - chn_right = SND_MIXER_SCHN_UNKNOWN; - } else { - if (!snd_mixer_selem_has_capture_channel(elem, chn_right)) - chn_right = SND_MIXER_SCHN_UNKNOWN; - } - } - - vleft = vright = 0; - if (type != MIXER_ELEM_CAPTURE && snd_mixer_selem_has_playback_volume(elem)) { - long vmin, vmax; - snd_mixer_selem_get_playback_volume_range(elem, &vmin, &vmax); - snd_mixer_selem_get_playback_volume(elem, chn_left, &vleft); - vleft = mixer_conv(vleft, vmin, vmax, 0, 100); - if (chn_right != SND_MIXER_SCHN_UNKNOWN) { - snd_mixer_selem_get_playback_volume(elem, chn_right, &vright); - vright = mixer_conv(vright, vmin, vmax, 0, 100); - } else { - vright = vleft; - } - } - - if (type == MIXER_ELEM_CAPTURE && snd_mixer_selem_has_capture_volume(elem)) { - long vmin, vmax; - snd_mixer_selem_get_capture_volume_range(elem, &vmin, &vmax); - snd_mixer_selem_get_capture_volume(elem, chn_left, &vleft); - vleft = mixer_conv(vleft, vmin, vmax, 0, 100); - if (chn_right != SND_MIXER_SCHN_UNKNOWN) { - snd_mixer_selem_get_capture_volume(elem, chn_right, &vright); - vright = mixer_conv(vright, vmin, vmax, 0, 100); - } else { - vright = vleft; - } - } - - /* update the focused full bar name - */ - if (elem_index == mixer_focus_elem) { - char tmp[50]; - /* control muted? */ - swl = swr = 1; - extra_info = ""; - if (mixer_type[elem_index] & MIXER_ELEM_MUTE_SWITCH) { - snd_mixer_selem_get_playback_switch(elem, chn_left, &swl); - swr = swl; - if (chn_right != SND_MIXER_SCHN_UNKNOWN) - snd_mixer_selem_get_playback_switch(elem, chn_right, &swr); - extra_info = !swl && !swr ? " [Off]" : ""; - } - if (type == MIXER_ELEM_ENUM || type == MIXER_ELEM_CAPTURE_ENUM) { - /* FIXME: should show the item names of secondary and later channels... */ - unsigned int eidx, length; - tmp[0]=' '; - tmp[1]='['; - if (! snd_mixer_selem_get_enum_item(elem, 0, &eidx) && - ! snd_mixer_selem_get_enum_item_name(elem, eidx, sizeof(tmp) - 3, tmp+2)) { - tmp[sizeof(tmp)-2] = 0; - length=strlen(tmp); - tmp[length]=']'; - tmp[length+1]=0; - extra_info = tmp; - } - } - if (type != MIXER_ELEM_CAPTURE && snd_mixer_selem_has_playback_volume(elem)) { - long vdbleft, vdbright; - unsigned int length; - if (!snd_mixer_selem_get_playback_dB(elem, chn_left, &vdbleft)) { - char tmpl[10], tmpr[10]; - dB_value(tmpl, vdbleft); - if ((chn_right != SND_MIXER_SCHN_UNKNOWN) && - (!snd_mixer_selem_get_playback_dB(elem, chn_right, &vdbright))) { - dB_value(tmpr, vdbright); - snprintf(tmp, 48, " [dB gain=%s, %s]", tmpl, tmpr); - } else { - snprintf(tmp, 48, " [dB gain=%s]", tmpl); - } - tmp[sizeof(tmp)-2] = 0; - length=strlen(tmp); - tmp[length+1]=0; - extra_info = tmp; - } - } - if (type == MIXER_ELEM_CAPTURE && snd_mixer_selem_has_capture_volume(elem)) { - long vdbleft, vdbright; - unsigned int length; - if (!snd_mixer_selem_get_capture_dB(elem, chn_left, &vdbleft)) { - char tmpl[10], tmpr[10]; - dB_value(tmpl, vdbleft); - if ((chn_right != SND_MIXER_SCHN_UNKNOWN) && - (!snd_mixer_selem_get_capture_dB(elem, chn_right, &vdbright))) { - dB_value(tmpr, vdbright); - snprintf(tmp, 48, " [dB gain=%s, %s]", tmpl, tmpr); - } else { - snprintf(tmp, 48, " [dB gain=%s]", tmpl); - } - tmp[sizeof(tmp)-2] = 0; - length=strlen(tmp); - tmp[length+1]=0; - extra_info = tmp; - } - } - display_item_info(elem_index, sid, extra_info); - } - - /* get channel bar position - */ - if (!mixer_cbar_get_pos (elem_index, &x, &y)) - return; - - /* channel bar name - */ - display_item_name(x, y, elem_index, sid); - y--; - - /* enum list? */ - if (type == MIXER_ELEM_ENUM || type == MIXER_ELEM_CAPTURE_ENUM) { - display_enum_list(elem, y, x); - return; /* no more to display */ - } - - /* current channel values - */ - mixer_dc (DC_BACK); - mvaddstr (y, x, " "); - if (mixer_type[elem_index] & MIXER_ELEM_HAS_VOLUME) { - char string[4]; - mixer_dc (DC_TEXT); - if (chn_right == SND_MIXER_SCHN_UNKNOWN) { - /* mono */ - snprintf (string, sizeof(string), "%ld", vleft); - mvaddstr (y, x + 4 - strlen (string) / 2, string); - } else { - /* stereo */ - snprintf (string, sizeof(string), "%ld", vleft); - mvaddstr (y, x + 3 - strlen (string), string); - mixer_dc (DC_CBAR_FRAME); - mvaddch (y, x + 3, '<'); - mvaddch (y, x + 4, '>'); - mixer_dc (DC_TEXT); - snprintf (string, sizeof(string), "%ld", vright); - mvaddstr (y, x + 5, string); - } - } - y--; - - /* capture input? - */ - if (mixer_view == VIEW_CAPTURE || mixer_view == VIEW_CHANNELS) { - if ((mixer_type[elem_index] & MIXER_ELEM_CAPTURE_SWITCH) && - snd_mixer_selem_has_capture_switch(elem)) { - int has_r_sw = chn_right != SND_MIXER_SCHN_UNKNOWN && - snd_mixer_selem_has_capture_channel(elem, chn_right); - snd_mixer_selem_get_capture_switch(elem, chn_left, &swl); - if (has_r_sw) - snd_mixer_selem_get_capture_switch(elem, chn_right, &swr); - else - swr = swl; - draw_capture_switch(x, y, elem_index, swl, swr); - } else - draw_blank(x, y, 2); - y--; - } - - /* mute switch */ - if (mixer_view == VIEW_PLAYBACK || mixer_view == VIEW_CHANNELS) { - if (mixer_type[elem_index] & MIXER_ELEM_MUTE_SWITCH) { - snd_mixer_selem_get_playback_switch(elem, chn_left, &swl); - if (chn_right != SND_MIXER_SCHN_UNKNOWN) - snd_mixer_selem_get_playback_switch(elem, chn_right, &swr); - else - swr = swl; - draw_playback_switch(x, y, elem_index, swl, swr); - } else { - mixer_dc (DC_CBAR_FRAME); - mvaddstr (y, x + 2, " "); - draw_blank(x, y - 1, 2); - } - y -= 2; - } - - /* left/right volume bar - */ - if (mixer_type[elem_index] & MIXER_ELEM_HAS_VOLUME) - draw_volume_bar(x, y, elem_index, vleft, vright); - else { - if (mixer_view == VIEW_CAPTURE) - mvaddstr (y, x + 2, " "); - draw_blank(x, y - 1, mixer_cbar_height + 1); - } -} - -static void -mixer_update_cbars (void) -{ - static int o_x = 0; - static int o_y = 0; - int i, x, y; - - display_view_info(); - if (!mixer_cbar_get_pos (mixer_focus_elem, &x, &y)) - { - if (mixer_focus_elem < mixer_first_vis_elem) - mixer_first_vis_elem = mixer_focus_elem; - else if (mixer_focus_elem >= mixer_first_vis_elem + mixer_n_vis_elems) - mixer_first_vis_elem = mixer_focus_elem - mixer_n_vis_elems + 1; - mixer_cbar_get_pos (mixer_focus_elem, &x, &y); - } - if (mixer_first_vis_elem + mixer_n_vis_elems >= mixer_n_view_elems) { - mixer_first_vis_elem = mixer_n_view_elems - mixer_n_vis_elems; - if (mixer_first_vis_elem < 0) - mixer_first_vis_elem = 0; - mixer_cbar_get_pos (mixer_focus_elem, &x, &y); - } - mixer_write_cbar(mixer_focus_elem); - for (i = 0; i < mixer_n_vis_elems; i++) { - if (i + mixer_first_vis_elem >= mixer_n_view_elems) - continue; - mixer_update_cbar (i + mixer_first_vis_elem); - } - - /* draw focused cbar - */ - if (mixer_have_old_focus) - { - mixer_dc (DC_BACK); - mvaddstr (o_y, o_x, " "); - mvaddstr (o_y, o_x + 9, " "); - } - o_x = x - 1; - o_y = y; - mixer_dc (DC_FOCUS); - mvaddstr (o_y, o_x, "<"); - mvaddstr (o_y, o_x + 9, ">"); - mixer_have_old_focus = 1; -} - -static void -mixer_draw_frame (void) -{ - char string[128]; - int i; - int max_len; - - /* card name - */ - mixer_dc (DC_PROMPT); - mvaddstr (1, 2, "Card: "); - mixer_dc (DC_TEXT); - sprintf (string, "%s", mixer_card_name); - max_len = mixer_max_x - 2 - 6 - 2; - if ((int)strlen (string) > max_len) - string[max_len] = 0; - addstr (string); - - /* device name - */ - mixer_dc (DC_PROMPT); - mvaddstr (2, 2, "Chip: "); - mixer_dc (DC_TEXT); - sprintf (string, "%s", mixer_device_name); - max_len = mixer_max_x - 2 - 6 - 2; - if ((int)strlen (string) > max_len) - string[max_len] = 0; - addstr (string); - - /* lines - */ - mixer_dc (DC_FRAME); - for (i = 1; i < mixer_max_y - 1; i++) - { - mvaddch (i, 0, ACS_VLINE); - mvaddch (i, mixer_max_x - 1, ACS_VLINE); - } - for (i = 1; i < mixer_max_x - 1; i++) - { - mvaddch (0, i, ACS_HLINE); - mvaddch (mixer_max_y - 1, i, ACS_HLINE); - } - - /* corners - */ - mvaddch (0, 0, ACS_ULCORNER); - mvaddch (0, mixer_max_x - 1, ACS_URCORNER); - mvaddch (mixer_max_y - 1, 0, ACS_LLCORNER); - if (!mixer_no_lrcorner) - mvaddch (mixer_max_y - 1, mixer_max_x - 1, ACS_LRCORNER); - else - { - mvaddch (mixer_max_y - 2, mixer_max_x - 1, ACS_LRCORNER); - mvaddch (mixer_max_y - 2, mixer_max_x - 2, ACS_ULCORNER); - mvaddch (mixer_max_y - 1, mixer_max_x - 2, ACS_LRCORNER); - } - - /* left/right scroll indicators */ - switch (mixer_view) { - case VIEW_PLAYBACK: - case VIEW_CAPTURE: - case VIEW_CHANNELS: - if (mixer_cbar_height > 0) { - int ind_hgt = (mixer_cbar_height + 1) / 2; - int ind_ofs = mixer_max_y / 2 - ind_hgt/2; - /* left scroll possible? */ - if (mixer_first_vis_elem > 0) { - for (i = 0; i < ind_hgt; i++) - mvaddch (i + ind_ofs, 0, '<'); - } - /* right scroll possible? */ - if (mixer_first_vis_elem + mixer_n_vis_elems < mixer_n_view_elems) { - for (i = 0; i < ind_hgt; i++) - mvaddch (i + ind_ofs, mixer_max_x - 1, '>'); - } - } - break; - default: - break; - } - - /* program title - */ - sprintf (string, "%s v%s (Press Escape to quit)", PRGNAME_UPPER, VERSION); - max_len = strlen (string); - if (mixer_max_x >= max_len + 4) - { - mixer_dc (DC_PROMPT); - mvaddch (0, mixer_max_x / 2 - max_len / 2 - 1, '['); - mvaddch (0, mixer_max_x / 2 - max_len / 2 + max_len, ']'); - } - if (mixer_max_x >= max_len + 2) - { - mixer_dc (DC_TEXT); - mvaddstr (0, mixer_max_x / 2 - max_len / 2, string); - } -} - -static char* -mixer_offset_text (char **t, - int col, - int *length) -{ - char *p = *t; - char *r; - - while (*p && *p != '\n' && col--) - p++; - if (*p == '\n' || !*p) - { - if (*p == '\n') - p++; - *length = 0; - *t = p; - return p; - } - - r = p; - while (*r && *r != '\n' && (*length)--) - r++; - - *length = r - p; - while (*r && *r != '\n') - r++; - if (*r == '\n') - r++; - *t = r; - - return p; -} - -static void -mixer_show_text (char *title, - char *text, - int *xoffs, - int *yoffs) -{ - int tlines = 0, tcols = 0; - float hscroll, vscroll; - float hoffs, voffs; - char *p, *text_offs = text; - int x1, x2, y1, y2; - int i, n, l, r; - unsigned long block, stipple; - - /* coords - */ - x1 = 2; - x2 = mixer_max_x - 3; - y1 = 4; - y2 = mixer_max_y - 2; - - if ((y2 - y1) < 3 || (x2 - x1) < 3) - return; - - /* text dimensions - */ - l = 0; - for (p = text; *p; p++) - if (*p == '\n') - { - tlines++; - tcols = MAX (l, tcols); - l = 0; - } - else - l++; - tcols = MAX (l, tcols); - if (p > text && *(p - 1) != '\n') - tlines++; - - /* scroll areas / offsets - */ - l = x2 - x1 - 2; - if (l > tcols) - { - x1 += (l - tcols) / 2; - x2 = x1 + tcols + 1; - } - if (mixer_hscroll_delta) - { - *xoffs += mixer_hscroll_delta; - mixer_hscroll_delta = 0; - if (*xoffs < 0) - { - *xoffs = 0; - beep (); - } - else if (*xoffs > tcols - l - 1) - { - *xoffs = MAX (0, tcols - l - 1); - beep (); - } - } - if (tcols - l - 1 <= 0) - { - hscroll = 1; - hoffs = 0; - } - else - { - hscroll = ((float) l) / tcols; - hoffs = ((float) *xoffs) / (tcols - l - 1); - } - - l = y2 - y1 - 2; - if (l > tlines) - { - y1 += (l - tlines) / 2; - y2 = y1 + tlines + 1; - } - if (mixer_vscroll_delta) - { - *yoffs += mixer_vscroll_delta; - mixer_vscroll_delta = 0; - if (*yoffs < 0) - { - *yoffs = 0; - beep (); - } - else if (*yoffs > tlines - l - 1) - { - *yoffs = MAX (0, tlines - l - 1); - beep (); - } - } - if (tlines - l - 1 <= 0) - { - voffs = 0; - vscroll = 1; - } - else - { - vscroll = ((float) l) / tlines; - voffs = ((float) *yoffs) / (tlines - l - 1); - } - - /* colors - */ - mixer_dc (DC_ANY_4); - - /* corners - */ - mvaddch (y2, x2, ACS_LRCORNER); - mvaddch (y2, x1, ACS_LLCORNER); - mvaddch (y1, x1, ACS_ULCORNER); - mvaddch (y1, x2, ACS_URCORNER); - - /* left + upper border - */ - for (i = y1 + 1; i < y2; i++) - mvaddch (i, x1, ACS_VLINE); - for (i = x1 + 1; i < x2; i++) - mvaddch (y1, i, ACS_HLINE); - if (title) - { - l = strlen (title); - if (l <= x2 - x1 - 3) - { - mvaddch (y1, x1 + 1 + (x2 - x1 - l) / 2 - 1, '['); - mvaddch (y1, x1 + 1 + (x2 - x1 - l) / 2 + l, ']'); - } - if (l <= x2 - x1 - 1) - { - mixer_dc (DC_CBAR_LABEL); - mvaddstr (y1, x1 + 1 + (x2 - x1 - l) / 2, title); - } - mixer_dc (DC_ANY_4); - } - - stipple = ACS_CKBOARD; - block = ACS_BLOCK; - if (block == '#' && ACS_BOARD == '#') - { - block = stipple; - stipple = ACS_BLOCK; - } - - /* lower scroll border - */ - l = x2 - x1 - 1; - n = hscroll * l; - r = (hoffs + 1.0 / (2 * (l - n - 1))) * (l - n - 1); - for (i = 0; i < l; i++) - mvaddch (y2, i + x1 + 1, hscroll >= 1 ? ACS_HLINE : - i >= r && i <= r + n ? block : stipple); - - /* right scroll border - */ - l = y2 - y1 - 1; - n = vscroll * l; - r = (voffs + 1.0 / (2 * (l - n - 1))) * (l - n - 1); - for (i = 0; i < l; i++) - mvaddch (i + y1 + 1, x2, vscroll >= 1 ? ACS_VLINE : - i >= r && i <= r + n ? block : stipple); - - /* show text - */ - x1++; y1++; - for (i = 0; i < *yoffs; i++) - { - l = 0; - mixer_offset_text (&text_offs, 0, &l); - } - for (i = y1; i < y2; i++) - { - l = x2 - x1; - p = mixer_offset_text (&text_offs, *xoffs, &l); - n = x1; - while (l--) - mvaddch (i, n++, *p++); - while (n < x2) - mvaddch (i, n++, ' '); - } -} - -struct vbuffer -{ - char *buffer; - int size; - int len; -}; - -static void -vbuffer_kill (struct vbuffer *vbuf) -{ - if (vbuf->size) - free (vbuf->buffer); - vbuf->buffer = NULL; - vbuf->size = 0; - vbuf->len = 0; -} - -#define vbuffer_append_string(vb,str) vbuffer_append (vb, str, strlen (str)) -static void -vbuffer_append (struct vbuffer *vbuf, - char *text, - int len) -{ - if (vbuf->size - vbuf->len <= len) - { - vbuf->size += len + 1; - vbuf->buffer = realloc (vbuf->buffer, vbuf->size); - } - memcpy (vbuf->buffer + vbuf->len, text, len); - vbuf->len += len; - vbuf->buffer[vbuf->len] = 0; -} - -static int -vbuffer_append_file (struct vbuffer *vbuf, - char *name) -{ - int fd; - - fd = open (name, O_RDONLY); - if (fd >= 0) - { - char buffer[1025]; - int l; - - do - { - l = read (fd, buffer, 1024); - - vbuffer_append (vbuf, buffer, MAX (0, l)); - } - while (l > 0 || (l < 0 && (errno == EAGAIN || errno == EINTR))); - - close (fd); - - return 0; - } - else - return 1; -} - -static void -mixer_show_procinfo (void) -{ - struct vbuffer vbuf = { NULL, 0, 0 }; - - vbuffer_append_string (&vbuf, "\n"); - vbuffer_append_string (&vbuf, "/proc/asound/version:\n"); - vbuffer_append_string (&vbuf, "====================\n"); - if (vbuffer_append_file (&vbuf, "/proc/asound/version")) - { - vbuffer_kill (&vbuf); - mixer_procinfo_xoffs = mixer_procinfo_yoffs = 0; - mixer_show_text ("/proc", - " No /proc information available. ", - &mixer_procinfo_xoffs, &mixer_procinfo_yoffs); - return; - } - else - vbuffer_append_file (&vbuf, "/proc/asound/meminfo"); - - vbuffer_append_string (&vbuf, "\n"); - vbuffer_append_string (&vbuf, "/proc/asound/cards:\n"); - vbuffer_append_string (&vbuf, "===================\n"); - if (vbuffer_append_file (&vbuf, "/proc/asound/cards")) - vbuffer_append_string (&vbuf, "No information available.\n"); - - vbuffer_append_string (&vbuf, "\n"); - vbuffer_append_string (&vbuf, "/proc/asound/devices:\n"); - vbuffer_append_string (&vbuf, "=====================\n"); - if (vbuffer_append_file (&vbuf, "/proc/asound/devices")) - vbuffer_append_string (&vbuf, "No information available.\n"); - - vbuffer_append_string (&vbuf, "\n"); - vbuffer_append_string (&vbuf, "/proc/asound/oss/devices:\n"); - vbuffer_append_string (&vbuf, "=========================\n"); - if (vbuffer_append_file (&vbuf, "/proc/asound/oss/devices")) - vbuffer_append_string (&vbuf, "No information available.\n"); - - vbuffer_append_string (&vbuf, "\n"); - vbuffer_append_string (&vbuf, "/proc/asound/timers:\n"); - vbuffer_append_string (&vbuf, "====================\n"); - if (vbuffer_append_file (&vbuf, "/proc/asound/timers")) - vbuffer_append_string (&vbuf, "No information available.\n"); - - vbuffer_append_string (&vbuf, "\n"); - vbuffer_append_string (&vbuf, "/proc/asound/pcm:\n"); - vbuffer_append_string (&vbuf, "=================\n"); - if (vbuffer_append_file (&vbuf, "/proc/asound/pcm")) - vbuffer_append_string (&vbuf, "No information available.\n"); - - mixer_show_text ("/proc", vbuf.buffer, - &mixer_procinfo_xoffs, &mixer_procinfo_yoffs); - vbuffer_kill (&vbuf); -} - -static int -mixer_event (snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem) -{ - mixer_changed_state = 1; - return 0; -} - -static void -mixer_init (void) -{ - snd_ctl_card_info_t *hw_info; - snd_ctl_t *ctl_handle; - int err; - snd_ctl_card_info_alloca(&hw_info); - - if ((err = snd_ctl_open (&ctl_handle, card_id, 0)) < 0) - mixer_abort (ERR_OPEN, "snd_ctl_open", err); - if ((err = snd_ctl_card_info (ctl_handle, hw_info)) < 0) - mixer_abort (ERR_FCN, "snd_ctl_card_info", err); - snd_ctl_close (ctl_handle); - /* open mixer device - */ - if ((err = snd_mixer_open (&mixer_handle, 0)) < 0) - mixer_abort (ERR_FCN, "snd_mixer_open", err); - if (mixer_level == 0 && (err = snd_mixer_attach (mixer_handle, card_id)) < 0) - mixer_abort (ERR_FCN, "snd_mixer_attach", err); - if ((err = snd_mixer_selem_register (mixer_handle, mixer_level > 0 ? &mixer_options : NULL, NULL)) < 0) - mixer_abort (ERR_FCN, "snd_mixer_selem_register", err); - snd_mixer_set_callback (mixer_handle, mixer_event); - if ((err = snd_mixer_load (mixer_handle)) < 0) - mixer_abort (ERR_FCN, "snd_mixer_load", err); - - /* setup global variables - */ - strcpy(mixer_card_name, snd_ctl_card_info_get_name(hw_info)); - strcpy(mixer_device_name, snd_ctl_card_info_get_mixername(hw_info)); -} - -/* init mixer screen - */ -static void -recalc_screen_size (void) -{ - getmaxyx (mixer_window, mixer_max_y, mixer_max_x); - if (mixer_minimize) - { - mixer_max_x = MIXER_MIN_X; - mixer_max_y = MIXER_MIN_Y; - } - mixer_ofs_x = 2 /* extra begin padding: */ + 1; - - /* required allocations */ - mixer_n_vis_elems = (mixer_max_x - mixer_ofs_x * 2 + 1) / 9; - mixer_n_vis_elems = CLAMP (mixer_n_vis_elems, 1, mixer_n_view_elems); - mixer_extra_space = mixer_max_x - mixer_ofs_x * 2 + 1 - mixer_n_vis_elems * 9; - mixer_extra_space = MAX (0, mixer_extra_space / (mixer_n_vis_elems + 1)); - mixer_text_y = MIXER_TEXT_Y; - if (mixer_view == VIEW_PLAYBACK || mixer_view == VIEW_CHANNELS) - mixer_text_y += 2; /* row for mute switch */ - if (mixer_view == VIEW_CAPTURE || mixer_view == VIEW_CHANNELS) - mixer_text_y++; /* row for capture switch */ - if (mixer_text_y + MIXER_CBAR_STD_HGT < mixer_max_y) - mixer_cbar_height = MIXER_CBAR_STD_HGT + MAX (1, mixer_max_y - mixer_text_y - MIXER_CBAR_STD_HGT + 1) / 2; - else - mixer_cbar_height = MAX (1, mixer_max_y - mixer_text_y); -} - -static void -mixer_reinit (void) -{ - snd_mixer_elem_t *elem; - int idx, elem_index, i, j, selem_count; - snd_mixer_selem_id_t *sid; - snd_mixer_selem_id_t *focus_gid; - int focus_type = -1; - snd_mixer_selem_id_alloca(&focus_gid); - - if (!mixer_changed_state) - return; - if (mixer_sid) { - snd_mixer_selem_id_copy(focus_gid, (snd_mixer_selem_id_t *)(((char *)mixer_sid) + snd_mixer_selem_id_sizeof() * mixer_grpidx[mixer_focus_elem])); - focus_type = mixer_type[mixer_focus_elem] & MIXER_ELEM_TYPE_MASK; - } -__again: - mixer_changed_state = 0; - if (mixer_sid != NULL) - free(mixer_sid); - selem_count = snd_mixer_get_count(mixer_handle); - mixer_sid = malloc(snd_mixer_selem_id_sizeof() * selem_count); - if (mixer_sid == NULL) - mixer_abort (ERR_FCN, "malloc", 0); - - mixer_n_selems = 0; - for (elem = snd_mixer_first_elem(mixer_handle); elem; elem = snd_mixer_elem_next(elem)) { - sid = (snd_mixer_selem_id_t *)(((char *)mixer_sid) + snd_mixer_selem_id_sizeof() * mixer_n_selems); - if (mixer_changed_state) - goto __again; - if (!snd_mixer_selem_is_active(elem)) - continue; - snd_mixer_selem_get_id(elem, sid); - mixer_n_selems++; - } - - mixer_n_elems = 0; - for (idx = 0; idx < mixer_n_selems; idx++) { - int nelems_added = 0; - sid = (snd_mixer_selem_id_t *)(((char *)mixer_sid) + snd_mixer_selem_id_sizeof() * idx); - if (mixer_changed_state) - goto __again; - elem = snd_mixer_find_selem(mixer_handle, sid); - if (elem == NULL) - CHECK_ABORT (ERR_FCN, "snd_mixer_find_selem()", -EINVAL); - for (i = 0; i < MIXER_ELEM_CAPTURE; i++) { - int ok; - for (j = ok = 0; j < 2; j++) { - if (mixer_changed_state) - goto __again; - if (snd_mixer_selem_has_playback_channel(elem, mixer_elem_chn[i][j])) - ok++; - } - if (ok) { - nelems_added++; - mixer_n_elems++; - } - } - if (snd_mixer_selem_has_capture_volume(elem) || - (nelems_added == 0 && snd_mixer_selem_has_capture_switch(elem))) - mixer_n_elems++; - } - - if (mixer_type) - free(mixer_type); - mixer_type = (int *)calloc(mixer_n_elems, sizeof(int)); - if (mixer_type == NULL) - mixer_abort(ERR_FCN, "malloc", 0); - if (mixer_grpidx) - free(mixer_grpidx); - mixer_grpidx = (int *)calloc(mixer_n_elems, sizeof(int)); - if (mixer_grpidx == NULL) - mixer_abort(ERR_FCN, "malloc", 0); - elem_index = 0; - for (idx = 0; idx < mixer_n_selems; idx++) { - int nelems_added = 0; - sid = (snd_mixer_selem_id_t *)(((char *)mixer_sid) + snd_mixer_selem_id_sizeof() * idx); - if (mixer_changed_state) - goto __again; - elem = snd_mixer_find_selem(mixer_handle, sid); - if (elem == NULL) - CHECK_ABORT (ERR_FCN, "snd_mixer_find_selem()", -EINVAL); - if ( (mixer_view == VIEW_PLAYBACK) || (mixer_view == VIEW_CHANNELS) ) { - for (i = MIXER_ELEM_FRONT; i <= MIXER_ELEM_SIDE; i++) { - int ok; - for (j = ok = 0; j < 2; j++) { - if (mixer_changed_state) - goto __again; - if (snd_mixer_selem_has_playback_channel(elem, mixer_elem_chn[i][j])) - ok++; - } - if (ok) { - sid = (snd_mixer_selem_id_t *)(((char *)mixer_sid) + snd_mixer_selem_id_sizeof() * idx); - mixer_grpidx[elem_index] = idx; - if (snd_mixer_selem_is_enumerated(elem)) { - if (mixer_view == VIEW_PLAYBACK && - snd_mixer_selem_is_enum_capture(elem)) - continue; - mixer_type[elem_index] = MIXER_ELEM_ENUM; - } else { - mixer_type[elem_index] = i; - if (i == 0 && snd_mixer_selem_has_playback_switch(elem)) - mixer_type[elem_index] |= MIXER_ELEM_MUTE_SWITCH; - if (snd_mixer_selem_has_playback_volume(elem)) - mixer_type[elem_index] |= MIXER_ELEM_HAS_VOLUME; - } - if (mixer_view == VIEW_CHANNELS) { - if (nelems_added == 0 && - ! snd_mixer_selem_has_capture_volume(elem) && - snd_mixer_selem_has_capture_switch(elem)) - mixer_type[elem_index] |= MIXER_ELEM_CAPTURE_SWITCH; - } - elem_index++; - nelems_added++; - if (elem_index >= mixer_n_elems) - break; - } - } - } - - if ( (mixer_view == VIEW_CAPTURE) || (mixer_view == VIEW_CHANNELS) ) { - int do_add = 0; - if (snd_mixer_selem_has_capture_volume(elem) && - (mixer_view == VIEW_CAPTURE || !snd_mixer_selem_has_common_volume(elem))) - do_add = 1; - if (!do_add && - (nelems_added == 0 && snd_mixer_selem_has_capture_switch(elem)) && - (mixer_view == VIEW_CAPTURE || !snd_mixer_selem_has_common_switch(elem))) - do_add = 1; - if (!do_add && - mixer_view == VIEW_CAPTURE && snd_mixer_selem_is_enum_capture(elem)) - do_add = 1; - - if (do_add) { - mixer_grpidx[elem_index] = idx; - if (snd_mixer_selem_is_enum_capture(elem)) - mixer_type[elem_index] = MIXER_ELEM_CAPTURE_ENUM; - else { - mixer_type[elem_index] = MIXER_ELEM_CAPTURE; - if (nelems_added == 0 && snd_mixer_selem_has_capture_switch(elem)) - mixer_type[elem_index] |= MIXER_ELEM_CAPTURE_SWITCH; - if (nelems_added) - mixer_type[elem_index] |= MIXER_ELEM_CAPTURE_SUFFIX; - if (snd_mixer_selem_has_capture_volume(elem)) - mixer_type[elem_index] |= MIXER_ELEM_HAS_VOLUME; - } - elem_index++; - if (elem_index >= mixer_n_elems) - break; - } - } - } - - mixer_n_view_elems = elem_index; - recalc_screen_size(); - mixer_focus_elem = 0; - if (focus_type >= 0) { - for (elem_index = 0; elem_index < mixer_n_view_elems; elem_index++) { - sid = (snd_mixer_selem_id_t *)(((char *)mixer_sid) + snd_mixer_selem_id_sizeof() * mixer_grpidx[elem_index]); - if (!strcmp(snd_mixer_selem_id_get_name(focus_gid), - snd_mixer_selem_id_get_name(sid)) && - snd_mixer_selem_id_get_index(focus_gid) == - snd_mixer_selem_id_get_index(sid) && - (mixer_type[elem_index] & MIXER_ELEM_TYPE_MASK) == focus_type) { - mixer_focus_elem = elem_index; - break; - } - } - } - - if (mixer_changed_state) - goto __again; -} - -static void -mixer_init_window (void) -{ - /* initialize ncurses - */ - setlocale(LC_CTYPE, ""); - mixer_window = initscr (); - curs_set (0); /* hide the cursor */ - - mixer_no_lrcorner = tigetflag ("xenl") != 1 && tigetflag ("am") != 1; - - if (mixer_do_color) - mixer_do_color = has_colors (); - mixer_init_draw_contexts (); - - /* react on key presses - */ - cbreak (); - noecho (); - leaveok (mixer_window, TRUE); - keypad (mixer_window, TRUE); - GETCH_BLOCK (1); - - recalc_screen_size(); - - mixer_clear (TRUE); -} - -static void -mixer_resize (void) -{ - struct winsize winsz = { 0, }; - - mixer_needs_resize = 0; - - if (ioctl (fileno (stdout), TIOCGWINSZ, &winsz) >= 0 && - winsz.ws_row && winsz.ws_col) - { - keypad (mixer_window, FALSE); - leaveok (mixer_window, FALSE); - - endwin (); - - mixer_max_x = MAX (2, winsz.ws_col); - mixer_max_y = MAX (2, winsz.ws_row); - - /* humpf, i don't get it, if only the number of rows change, - * ncurses will segfault shortly after (could trigger that with mc as well). - */ - resizeterm (mixer_max_y + 1, mixer_max_x + 1); - resizeterm (mixer_max_y, mixer_max_x); - - mixer_init_window (); - - if (mixer_max_x < MIXER_MIN_X || - mixer_max_y < MIXER_MIN_Y) - beep (); // mixer_abort (ERR_WINSIZE, ""); - - mixer_have_old_focus = 0; - } -} - -static void -mixer_set_delta(int delta) -{ - int grp; - - for (grp = 0; grp < 2; grp++) - mixer_volume_delta[grp] = delta; -} - -static void -mixer_add_delta(int delta) -{ - int grp; - - for (grp = 0; grp < 2; grp++) - mixer_volume_delta[grp] += delta; -} - -static int -mixer_iteration (void) -{ - int count, err; - struct pollfd *fds; - int finished = 0; - int key = 0; - int old_view; - unsigned short revents; - - /* setup for select on stdin and the mixer fd */ - if ((count = snd_mixer_poll_descriptors_count(mixer_handle)) < 0) - mixer_abort (ERR_FCN, "snd_mixer_poll_descriptors_count", count); - fds = calloc(count + 1, sizeof(struct pollfd)); - if (fds == NULL) - mixer_abort (ERR_FCN, "malloc", 0); - fds->fd = fileno(stdin); - fds->events = POLLIN; - if ((err = snd_mixer_poll_descriptors(mixer_handle, fds + 1, count)) < 0) - mixer_abort (ERR_FCN, "snd_mixer_poll_descriptors", err); - if (err != count) - mixer_abort (ERR_FCN, "snd_mixer_poll_descriptors (err != count)", 0); - - finished = poll(fds, count + 1, -1); - - /* don't abort on handled signals */ - if (finished < 0 && errno == EINTR) - finished = 0; - if (mixer_needs_resize) - mixer_resize (); - - if (finished > 0) { - if (fds->revents & POLLIN) { - key = getch (); - finished--; - } - } else { - key = 0; - } - - if (finished > 0) { - if (snd_mixer_poll_descriptors_revents(mixer_handle, fds + 1, count, &revents) >= 0) { - if (revents & POLLNVAL) - mixer_abort (ERR_FCN, "snd_mixer_poll_descriptors (POLLNVAL)", 0); - if (revents & POLLERR) - mixer_abort (ERR_FCN, "snd_mixer_poll_descriptors (POLLERR)", 0); - if (revents & POLLIN) - snd_mixer_handle_events(mixer_handle); - } - } - - finished = 0; - free(fds); - - old_view = mixer_view; - -#if 0 /* DISABLED: it's not so usefull rather annoying... */ - /* feature Escape prefixing for some keys */ - if (key == 27) - { - GETCH_BLOCK (0); - key = getch (); - GETCH_BLOCK (1); - switch (key) - { - case 9: /* Tab */ - key = KEY_BTAB; - break; - default: - key = 27; - break; - } - } -#endif /* DISABLED */ - - /* general keys */ - switch (key) - { - case 0: - /* ignore */ - break; - case 27: /* Escape */ - case KEY_F (10): - finished = 1; - key = 0; - break; - case 13: /* Return */ - case 10: /* NewLine */ - if (mixer_view != mixer_view_saved) { - mixer_view = mixer_view_saved; - mixer_changed_state=1; - mixer_reinit (); - } - key = 0; - break; - case 'h': - case 'H': - case '?': - case KEY_F (1): - mixer_view = VIEW_HELP; - key = 0; - break; - case '/': - case KEY_F (2): - mixer_view = VIEW_PROCINFO; - key = 0; - break; - case KEY_F (3): - if (mixer_view == VIEW_PLAYBACK) { - mixer_clear (FALSE); - } else { - mixer_view = mixer_view_saved = VIEW_PLAYBACK; - mixer_changed_state=1; - mixer_reinit (); - } - key = 0; - break; - case KEY_F (4): - if (mixer_view == VIEW_CAPTURE) { - mixer_clear (FALSE); - } else { - mixer_view = mixer_view_saved = VIEW_CAPTURE; - mixer_changed_state=1; - mixer_reinit (); - } - key = 0; - break; - case KEY_F (5): - if (mixer_view == VIEW_CHANNELS) { - mixer_clear (FALSE); - } else { - mixer_view = mixer_view_saved = VIEW_CHANNELS; - mixer_changed_state=1; - mixer_reinit (); - } - key = 0; - break; - case 9: /* Tab */ - if (mixer_view >= VIEW_CHANNELS && mixer_view <= VIEW_CAPTURE) { - mixer_view = (mixer_view + 1) % 3 + VIEW_CHANNELS; - mixer_view_saved = mixer_view; - mixer_changed_state = 1; - mixer_reinit (); - key = 0; - } - break; - case '\014': - case 'L': - case 'l': - mixer_clear (TRUE); - break; - } - - if (key && (mixer_view == VIEW_HELP || - mixer_view == VIEW_PROCINFO)) - switch (key) - { - case 9: /* Tab */ - mixer_hscroll_delta += 8; - break; - case KEY_BTAB: - mixer_hscroll_delta -= 8; - break; - case KEY_A1: - mixer_hscroll_delta -= 1; - mixer_vscroll_delta -= 1; - break; - case KEY_A3: - mixer_hscroll_delta += 1; - mixer_vscroll_delta -= 1; - break; - case KEY_C1: - mixer_hscroll_delta -= 1; - mixer_vscroll_delta += 1; - break; - case KEY_C3: - mixer_hscroll_delta += 1; - mixer_vscroll_delta += 1; - break; - case KEY_RIGHT: - case 'n': - mixer_hscroll_delta += 1; - break; - case KEY_LEFT: - case 'p': - mixer_hscroll_delta -= 1; - break; - case KEY_UP: - case 'k': - case 'w': - case 'W': - mixer_vscroll_delta -= 1; - break; - case KEY_DOWN: - case 'j': - case 'x': - case 'X': - mixer_vscroll_delta += 1; - break; - case KEY_PPAGE: - case 'B': - case 'b': - mixer_vscroll_delta -= (mixer_max_y - 5) / 2; - break; - case KEY_NPAGE: - case ' ': - mixer_vscroll_delta += (mixer_max_y - 5) / 2; - break; - case KEY_BEG: - case KEY_HOME: - mixer_hscroll_delta -= 0xffffff; - break; - case KEY_LL: - case KEY_END: - mixer_hscroll_delta += 0xffffff; - break; - } - - if (key && - ((mixer_view == VIEW_CHANNELS) || - (mixer_view == VIEW_PLAYBACK) || - (mixer_view == VIEW_CAPTURE)) ) - switch (key) - { - case KEY_RIGHT: - case 'n': - mixer_focus_elem += 1; - break; - case KEY_LEFT: - case 'p': - mixer_focus_elem -= 1; - break; - case KEY_PPAGE: - mixer_set_delta(5); - break; - case KEY_NPAGE: - mixer_set_delta(-5); - break; -#if 0 - case KEY_BEG: - case KEY_HOME: - mixer_set_delta(100); - break; -#endif - case KEY_LL: - case KEY_END: - mixer_set_delta(-100); - break; - case '+': - mixer_set_delta(1); - break; - case '-': - mixer_set_delta(-1); - break; - case 'w': - case KEY_UP: - case 'k': - mixer_set_delta(1); - case 'W': - mixer_add_delta(1); - break; - case 'x': - case KEY_DOWN: - case 'j': - mixer_set_delta(-1); - case 'X': - mixer_add_delta(-1); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - mixer_volume_absolute = 10 * (key - '0'); - break; - case 'q': - mixer_volume_delta[MIXER_CHN_LEFT] = 1; - case 'Q': - mixer_volume_delta[MIXER_CHN_LEFT] += 1; - break; - case 'y': - case 'z': - mixer_volume_delta[MIXER_CHN_LEFT] = -1; - case 'Y': - case 'Z': - mixer_volume_delta[MIXER_CHN_LEFT] += -1; - break; - case 'e': - mixer_volume_delta[MIXER_CHN_RIGHT] = 1; - case 'E': - mixer_volume_delta[MIXER_CHN_RIGHT] += 1; - break; - case 'c': - mixer_volume_delta[MIXER_CHN_RIGHT] = -1; - case 'C': - mixer_volume_delta[MIXER_CHN_RIGHT] += -1; - break; - case 'm': - case 'M': - mixer_toggle_mute |= MIXER_MASK_STEREO; - break; - case 'b': - case 'B': - case '=': - mixer_balance_volumes = 1; - break; - case '<': - case ',': - mixer_toggle_mute |= MIXER_MASK_LEFT; - break; - case '>': - case '.': - mixer_toggle_mute |= MIXER_MASK_RIGHT; - break; - case ' ': - mixer_toggle_capture |= MIXER_MASK_STEREO; - break; - case KEY_IC: - case ';': - mixer_toggle_capture |= MIXER_MASK_LEFT; - break; - case '\'': - case KEY_DC: - mixer_toggle_capture |= MIXER_MASK_RIGHT; - break; - } - - if (old_view != mixer_view) - mixer_clear (FALSE); - - if (! mixer_n_view_elems) - mixer_focus_elem = 0; - else - mixer_focus_elem = CLAMP (mixer_focus_elem, 0, mixer_n_view_elems - 1); - - return finished; -} - -static void -mixer_winch (void) -{ - signal (SIGWINCH, (void*) mixer_winch); - - mixer_needs_resize++; -} - -static void -mixer_signal_handler (int signal) -{ - if (signal != SIGSEGV) - mixer_abort (ERR_SIGNAL, strsignal(signal), 0); - else - { - fprintf (stderr, "\nSegmentation fault.\n"); - _exit (11); - } -} - -int -main (int argc, - char **argv) -{ - int opt; - - /* parse args - */ - do - { - opt = getopt (argc, argv, "c:D:shgV:a:"); - switch (opt) - { - case '?': - case 'h': - printf ("%s v%s\n", PRGNAME_UPPER, VERSION); - printf ("Usage: %s [-h] [-c ] [-D ] [-g] [-s] [-V ] [-a ]\n", PRGNAME); - return 1; - case 'c': - { - int i = snd_card_get_index(optarg); - if (i < 0 || i > 31) { - fprintf (stderr, "wrong -c argument '%s'\n", optarg); - mixer_abort (ERR_NONE, "", 0); - } - sprintf(card_id, "hw:%i", i); - } - break; - case 'D': - strncpy(card_id, optarg, sizeof(card_id)); - card_id[sizeof(card_id)-1] = '\0'; - break; - case 'g': - mixer_do_color = !mixer_do_color; - break; - case 's': - mixer_minimize = 1; - break; - case 'V': - if (*optarg == 'p' || *optarg == 'P') - mixer_view = VIEW_PLAYBACK; - else if (*optarg == 'c' || *optarg == 'C') - mixer_view = VIEW_CAPTURE; - else - mixer_view = VIEW_CHANNELS; - break; - case 'a': - mixer_level = 1; - memset(&mixer_options, 0, sizeof(mixer_options)); - mixer_options.ver = 1; - if (!strcmp(optarg, "none")) - mixer_options.abstract = SND_MIXER_SABSTRACT_NONE; - else if (!strcmp(optarg, "basic")) - mixer_options.abstract = SND_MIXER_SABSTRACT_BASIC; - else { - fprintf(stderr, "Select correct abstraction level (none or basic)...\n"); - mixer_abort (ERR_NONE, "", 0); - } - break; - } - } - while (opt > 0); - - mixer_options.device = card_id; - - /* initialize mixer - */ - mixer_init (); - mixer_reinit (); - - if (mixer_n_elems == 0) { - fprintf(stderr, "No mixer elems found\n"); - mixer_abort (ERR_NONE, "", 0); - } - - /* setup signal handlers - */ - signal (SIGINT, mixer_signal_handler); - signal (SIGTRAP, mixer_signal_handler); - // signal (SIGABRT, mixer_signal_handler); - signal (SIGQUIT, mixer_signal_handler); - signal (SIGBUS, mixer_signal_handler); - signal (SIGSEGV, mixer_signal_handler); - signal (SIGPIPE, mixer_signal_handler); - signal (SIGTERM, mixer_signal_handler); - - /* initialize ncurses - */ - mixer_init_window (); - if (mixer_max_x < MIXER_MIN_X || - mixer_max_y < MIXER_MIN_Y) - beep (); // mixer_abort (ERR_WINSIZE, ""); - - signal (SIGWINCH, (void*) mixer_winch); - - do - { - /* draw window upon every iteration */ - if (!mixer_needs_resize) - { - switch (mixer_view) - { - case VIEW_CHANNELS: - case VIEW_PLAYBACK: - case VIEW_CAPTURE: - mixer_update_cbars (); - break; - case VIEW_HELP: - mixer_show_text ("Help", mixer_help_text, &mixer_help_xoffs, &mixer_help_yoffs); - break; - case VIEW_PROCINFO: - mixer_show_procinfo (); - break; - } - mixer_draw_frame (); - refresh (); - } - } - while (!mixer_iteration ()); - - mixer_abort (ERR_NONE, "", 0); -} diff --git a/alsamixer/card_select.c b/alsamixer/card_select.c new file mode 100644 index 0000000..b473dcf --- /dev/null +++ b/alsamixer/card_select.c @@ -0,0 +1,268 @@ +/* + * card_select.c - select a card by list or device name + * Copyright (c) Clemens Ladisch + * + * 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 . + */ + +#include "aconfig.h" +#include +#include +#include +#include +#include +#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); +} diff --git a/alsamixer/card_select.h b/alsamixer/card_select.h new file mode 100644 index 0000000..4ba15fc --- /dev/null +++ b/alsamixer/card_select.h @@ -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 diff --git a/alsamixer/cli.c b/alsamixer/cli.c new file mode 100644 index 0000000..ab6255f --- /dev/null +++ b/alsamixer/cli.c @@ -0,0 +1,135 @@ +/* + * alsamixer - curses mixer for the ALSA project + * Copyright (c) 1998,1999 Tim Janik + * Jaroslav Kysela + * Copyright (c) 2009 Clemens Ladisch + * + * 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 . + */ + +#include "aconfig.h" +#include +#include +#include +#include +#include +#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; +} diff --git a/alsamixer/colors.c b/alsamixer/colors.c new file mode 100644 index 0000000..f68de54 --- /dev/null +++ b/alsamixer/colors.c @@ -0,0 +1,103 @@ +/* + * colors.c - color and attribute definitions + * Copyright (c) 1998,1999 Tim Janik + * Jaroslav Kysela + * Copyright (c) 2009 Clemens Ladisch + * + * 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 . + */ + +#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; + } +} diff --git a/alsamixer/colors.h b/alsamixer/colors.h new file mode 100644 index 0000000..e1d3b1a --- /dev/null +++ b/alsamixer/colors.h @@ -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 diff --git a/alsamixer/device_name.c b/alsamixer/device_name.c new file mode 100644 index 0000000..c58e652 --- /dev/null +++ b/alsamixer/device_name.c @@ -0,0 +1,197 @@ +/* + * device_name_form.c - ask for sound control device name + * Copyright (c) Clemens Ladisch + * + * 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 . + */ + +#include "aconfig.h" +#include +#include +#include CURSESINC +#include +#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); +} diff --git a/alsamixer/device_name.h b/alsamixer/device_name.h new file mode 100644 index 0000000..f4a1f3f --- /dev/null +++ b/alsamixer/device_name.h @@ -0,0 +1,6 @@ +#ifndef DEVICE_NAME_FORM_H_INCLUDED +#define DEVICE_NAME_FORM_H_INCLUDED + +void create_device_name_form(void); + +#endif diff --git a/alsamixer/die.c b/alsamixer/die.c new file mode 100644 index 0000000..dcd8536 --- /dev/null +++ b/alsamixer/die.c @@ -0,0 +1,39 @@ +/* + * die.c - error handlers + * Copyright (c) Clemens Ladisch + * + * 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 . + */ + +#include "aconfig.h" +#include +#include +#include +#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); +} diff --git a/alsamixer/die.h b/alsamixer/die.h new file mode 100644 index 0000000..39ef1c0 --- /dev/null +++ b/alsamixer/die.h @@ -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 diff --git a/alsamixer/mainloop.c b/alsamixer/mainloop.c new file mode 100644 index 0000000..7a5ffdc --- /dev/null +++ b/alsamixer/mainloop.c @@ -0,0 +1,135 @@ +/* + * mainloop.c - main loop + * Copyright (c) Clemens Ladisch + * + * 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 . + */ + +#include "aconfig.h" +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/alsamixer/mainloop.h b/alsamixer/mainloop.h new file mode 100644 index 0000000..0cfc989 --- /dev/null +++ b/alsamixer/mainloop.h @@ -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 diff --git a/alsamixer/mem.c b/alsamixer/mem.c new file mode 100644 index 0000000..fa03a89 --- /dev/null +++ b/alsamixer/mem.c @@ -0,0 +1,68 @@ +/* + * mem.c - memory allocation checkers + * Copyright (c) Clemens Ladisch + * + * 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 . + */ + +#define _GNU_SOURCE +#include "aconfig.h" +#include +#include +#include +#include +#include +#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; +} diff --git a/alsamixer/mem.h b/alsamixer/mem.h new file mode 100644 index 0000000..d0e5f54 --- /dev/null +++ b/alsamixer/mem.h @@ -0,0 +1,11 @@ +#ifndef MEM_H_INCLUDED +#define MEM_H_INCLUDED + +#include + +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 diff --git a/alsamixer/mixer_controls.c b/alsamixer/mixer_controls.c new file mode 100644 index 0000000..796df7b --- /dev/null +++ b/alsamixer/mixer_controls.c @@ -0,0 +1,521 @@ +/* + * mixer_controls.c - handles mixer controls and mapping from selems + * Copyright (c) 1998,1999 Tim Janik + * Jaroslav Kysela + * Copyright (c) 2009 Clemens Ladisch + * + * 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 . + */ + +#include "aconfig.h" +#include +#include +#include +#include CURSESINC +#include +#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(); +} diff --git a/alsamixer/mixer_controls.h b/alsamixer/mixer_controls.h new file mode 100644 index 0000000..dbb3a9d --- /dev/null +++ b/alsamixer/mixer_controls.h @@ -0,0 +1,37 @@ +#ifndef MIXER_CONTROLS_H_INCLUDED +#define MIXER_CONTROLS_H_INCLUDED + +#include + +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 diff --git a/alsamixer/mixer_display.c b/alsamixer/mixer_display.c new file mode 100644 index 0000000..aade71d --- /dev/null +++ b/alsamixer/mixer_display.c @@ -0,0 +1,739 @@ +/* + * mixer_display.c - handles displaying of mixer widget and controls + * Copyright (c) 1874 Lewis Carroll + * Copyright (c) 2009 Clemens Ladisch + * + * 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 . + */ + +#include "aconfig.h" +#include +#include +#include +#include CURSESINC +#include +#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; +} diff --git a/alsamixer/mixer_display.h b/alsamixer/mixer_display.h new file mode 100644 index 0000000..3d65670 --- /dev/null +++ b/alsamixer/mixer_display.h @@ -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 diff --git a/alsamixer/mixer_widget.c b/alsamixer/mixer_widget.c new file mode 100644 index 0000000..796ea1d --- /dev/null +++ b/alsamixer/mixer_widget.c @@ -0,0 +1,680 @@ +/* + * mixer_widget.c - mixer widget and keys handling + * Copyright (c) 1998,1999 Tim Janik + * Jaroslav Kysela + * Copyright (c) 2009 Clemens Ladisch + * + * 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 . + */ + +#include "aconfig.h" +#include +#include +#include +#include +#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 "), + _(" Jaroslav Kysela "), + _(" Clemens Ladisch "), + }; + 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(); +} diff --git a/alsamixer/mixer_widget.h b/alsamixer/mixer_widget.h new file mode 100644 index 0000000..da8628e --- /dev/null +++ b/alsamixer/mixer_widget.h @@ -0,0 +1,36 @@ +#ifndef MIXER_WIDGET_H_INCLUDED +#define MIXER_WIDGET_H_INCLUDED + +#include CURSESINC +#include +#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 diff --git a/alsamixer/proc_files.c b/alsamixer/proc_files.c new file mode 100644 index 0000000..b2f5f21 --- /dev/null +++ b/alsamixer/proc_files.c @@ -0,0 +1,169 @@ +/* + * proc_files.c - shows ALSA system information files + * Copyright (c) Clemens Ladisch + * + * 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 . + */ + +#include "aconfig.h" +#include +#include +#include +#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); +} diff --git a/alsamixer/proc_files.h b/alsamixer/proc_files.h new file mode 100644 index 0000000..8862c71 --- /dev/null +++ b/alsamixer/proc_files.h @@ -0,0 +1,6 @@ +#ifndef PROC_FILES_H_INCLUDED +#define PROC_FILES_H_INCLUDED + +void create_proc_files_list(void); + +#endif diff --git a/alsamixer/textbox.c b/alsamixer/textbox.c new file mode 100644 index 0000000..024aa73 --- /dev/null +++ b/alsamixer/textbox.c @@ -0,0 +1,396 @@ +/* + * textbox.c - show a text box for messages, files or help + * Copyright (c) 1998,1999 Tim Janik + * Jaroslav Kysela + * Copyright (c) 2009 Clemens Ladisch + * + * 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 . + */ + +#include "aconfig.h" +#include +#include +#include +#include +#include CURSESINC +#include +#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(); +} diff --git a/alsamixer/textbox.h b/alsamixer/textbox.h new file mode 100644 index 0000000..7dc290b --- /dev/null +++ b/alsamixer/textbox.h @@ -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 diff --git a/alsamixer/utils.c b/alsamixer/utils.c new file mode 100644 index 0000000..3602bef --- /dev/null +++ b/alsamixer/utils.c @@ -0,0 +1,111 @@ +/* + * utils.c - multibyte-string helpers + * Copyright (c) Clemens Ladisch + * + * 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 . + */ + +#define _XOPEN_SOURCE +#include "aconfig.h" +#include +#include +#include +#include +#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; +} diff --git a/alsamixer/utils.h b/alsamixer/utils.h new file mode 100644 index 0000000..00a52dd --- /dev/null +++ b/alsamixer/utils.h @@ -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 diff --git a/alsamixer/widget.c b/alsamixer/widget.c new file mode 100644 index 0000000..75da4c2 --- /dev/null +++ b/alsamixer/widget.c @@ -0,0 +1,140 @@ +/* + * widget.c - handles widget objects and the widget stack + * Copyright (c) Clemens Ladisch + * + * 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 . + */ + +#include "aconfig.h" +#include +#include +#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(); + } +} diff --git a/alsamixer/widget.h b/alsamixer/widget.h new file mode 100644 index 0000000..6adb526 --- /dev/null +++ b/alsamixer/widget.h @@ -0,0 +1,33 @@ +#ifndef WIDGET_H_INCLUDED +#define WIDGET_H_INCLUDED + +#include + +#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 diff --git a/configure.in b/configure.in index 5cb22a9..1349ff3 100644 --- a/configure.in +++ b/configure.in @@ -1,6 +1,6 @@ dnl Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT(alsamixer/alsamixer.c) +AC_INIT(aplay/aplay.c) AC_PREFIX_DEFAULT(/usr) AM_INIT_AUTOMAKE(alsa-utils, 1.0.20) @@ -105,23 +105,32 @@ if test x$alsamixer = xtrue; then [ --with-curses libname Specify the curses library to use (default=auto)], curseslib="$withval", curseslib="auto") - if test "$curseslib" = "ncursesw"; then + CURSESLIBDIR="" + NCURSESLIBSUFFIX="" + CURSES_NLS="no" + if test "$curseslib" = "ncursesw" -o \( "$curseslib" = "auto" -a "$USE_NLS" = "yes" \); then AC_CHECK_PROG([ncursesw5_config], [ncursesw5-config], [yes]) if test "$ncursesw5_config" = "yes"; then CURSESINC="" CURSESLIB=`ncursesw5-config --libs` + CURSESLIBDIR=`ncursesw5-config --libdir` CURSES_CFLAGS=`ncursesw5-config --cflags` curseslib="ncursesw" else AC_CHECK_LIB(ncursesw, initscr, [ CURSESINC=''; CURSESLIB='-lncursesw'; curseslib="ncursesw"]) fi + if test -n "$CURSESINC"; then + NCURSESLIBSUFFIX="w" + CURSES_NLS="yes" + fi fi if test "$curseslib" = "ncurses" -o "$curseslib" = "auto"; then AC_CHECK_PROG([ncurses5_config], [ncurses5-config], [yes]) if test "$ncurses5_config" = "yes"; then CURSESINC="" CURSESLIB=`ncurses5-config --libs` + CURSESLIBDIR=`ncurses5-config --libdir` CURSES_CFLAGS=`ncurses5-config --cflags` curseslib="ncurses" else @@ -136,6 +145,78 @@ if test x$alsamixer = xtrue; then if test -z "$CURSESINC"; then AC_MSG_ERROR(this packages requires a curses library) fi + + AC_MSG_CHECKING([for curses library]) + AC_MSG_RESULT([$curseslib]) + AC_MSG_CHECKING([for curses header name]) + AC_MSG_RESULT([$CURSESINC]) + AC_MSG_CHECKING([for curses compiler flags]) + AC_MSG_RESULT([$CURSES_CFLAGS]) + + dnl CURSESLIBS might have the library path at the beginning. If so, we cut it + dnl off so that we can insert the other curses libraries before the ncurses + dnl library but after the library path (which is later again prepended below). + if test -n "$CURSESLIBDIR"; then + if test "-L$CURSESLIBDIR " = "$(echo $CURSESLIB | cut -c-$((${#CURSESLIBDIR}+3)) )"; then + CURSESLIB="$(echo $CURSESLIB | cut -c$((${#CURSESLIBDIR}+4))-)" + fi + fi + + saved_CFLAGS="$CFLAGS" + saved_LDFLAGS="$LDFLAGS" + saved_LIBS="$LIBS" + CFLAGS="$CFLAGS $CURSES_CFLAGS" + if test -n "$CURSESLIBDIR"; then + LDFLAGS="$LDFLAGS -L$CURSESLIBDIR" + fi + LIBS="$CURSESLIB $LIBS" + + if test "$USE_NLS" = "yes"; then + AC_MSG_CHECKING([for curses NLS support]) + dnl In theory, a single-byte curses works just fine in ISO 8859-* locales. + dnl In practice, however, everybody uses UTF-8 nowadays, so we'd better + dnl check for wide-character support. + dnl For ncurses/ncursesw, CURSES_NLS was already set above. + if test "$curseslib" = "curses"; then + AC_TRY_LINK([ + #define _XOPEN_SOURCE 1 + #define _XOPEN_SOURCE_EXTENDED 1 + #include + ], [ + cchar_t wc; + setcchar(&wc, L"x", A_NORMAL, 0, 0); + ], + [CURSES_NLS="yes"]) + fi + AC_MSG_RESULT([$CURSES_NLS]) + if test "$CURSES_NLS" = "yes"; then + AC_DEFINE([ENABLE_NLS_IN_CURSES], [1], + [Define if curses-based programs can show translated messages.]) + fi + fi + + AC_CHECK_HEADERS([panel.h menu.h form.h], [], + [AC_MSG_ERROR([required curses helper header not found])]) + AC_CHECK_LIB([panel$NCURSESLIBSUFFIX], [new_panel], + [CURSESLIB="-lpanel$NCURSESLIBSUFFIX $CURSESLIB"], + [AC_MSG_ERROR([panel$NCURSESLIBSUFFIX library not found])]) + AC_CHECK_LIB([menu$NCURSESLIBSUFFIX], [new_menu], + [CURSESLIB="-lmenu$NCURSESLIBSUFFIX $CURSESLIB"], + [AC_MSG_ERROR([menu$NCURSESLIBSUFFIX library not found])]) + AC_CHECK_LIB([form$NCURSESLIBSUFFIX], [new_form], + [CURSESLIB="-lform$NCURSESLIBSUFFIX $CURSESLIB"], + [AC_MSG_ERROR([form$NCURSESLIBSUFFIX library not found])]) + + CFLAGS="$saved_CFLAGS" + LDFLAGS="$saved_LDFLAGS" + LIBS="$saved_LIBS" + + if test -n "$CURSESLIBDIR"; then + CURSESLIB="-L$CURSESLIBDIR $CURSESLIB" + fi + + AC_MSG_CHECKING([for curses linker flags]) + AC_MSG_RESULT([$CURSESLIB]) fi AC_SUBST(CURSESINC) diff --git a/include/Makefile.am b/include/Makefile.am index 112e5ce..7a3968d 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1,4 +1,4 @@ -noinst_HEADERS=version.h gettext.h +noinst_HEADERS=version.h gettext.h gettext_curses.h version.h: stamp-vh @: diff --git a/include/gettext.h b/include/gettext.h index c2bfe96..d8a1467 100644 --- a/include/gettext.h +++ b/include/gettext.h @@ -1,7 +1,13 @@ #ifndef __MY_GETTEXT_H #define __MY_GETTEXT_H -#if ENABLE_NLS +#ifdef USES_CURSES +#define ENABLE_NLS_TEST ENABLE_NLS_IN_CURSES +#else +#define ENABLE_NLS_TEST ENABLE_NLS +#endif + +#if ENABLE_NLS_TEST # include #else # define gettext(msgid) (msgid) diff --git a/include/gettext_curses.h b/include/gettext_curses.h new file mode 100644 index 0000000..f1c4041 --- /dev/null +++ b/include/gettext_curses.h @@ -0,0 +1,2 @@ +#define USES_CURSES +#include "gettext.h" diff --git a/po/Makevars b/po/Makevars index 32692ab..779d8ac 100644 --- a/po/Makevars +++ b/po/Makevars @@ -18,7 +18,7 @@ XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ # or entity, or to disclaim their copyright. The empty string stands for # the public domain; in this case the translators are expected to disclaim # their copyright. -COPYRIGHT_HOLDER = Free Software Foundation, Inc. +COPYRIGHT_HOLDER = The ALSA Team # This is the email address or URL to which the translators shall report # bugs in the untranslated strings: diff --git a/po/POTFILES.in b/po/POTFILES.in index 11a6a96..3d1b8ea 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,3 +1,11 @@ +alsamixer/card_select.c +alsamixer/cli.c +alsamixer/device_name.c +alsamixer/die.c +alsamixer/mixer_display.c +alsamixer/mixer_widget.c +alsamixer/proc_files.c +alsamixer/textbox.c aplay/aplay.c seq/aconnect/aconnect.c seq/aseqnet/aseqnet.c diff --git a/po/ja.po b/po/ja.po index 3b2e319..5a10ee7 100644 --- a/po/ja.po +++ b/po/ja.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: alsa-utils 1.0.9a\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2006-11-11 10:45+0000\n" +"POT-Creation-Date: 2009-05-24 19:56+0200\n" "PO-Revision-Date: 2006-04-18 15:51+0200\n" "Last-Translator: Takashi Iwai \n" "Language-Team: Japanese\n" @@ -17,23 +17,363 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: aplay/aplay.c:128 +#: alsamixer/card_select.c:126 alsamixer/device_name.c:126 +msgid "Sound Card" +msgstr "" + +#: alsamixer/card_select.c:181 +msgid "(default)" +msgstr "" + +#: alsamixer/card_select.c:191 +msgid "cannot enumerate sound cards" +msgstr "" + +#: alsamixer/card_select.c:215 +msgid "enter device name..." +msgstr "" + +#: alsamixer/cli.c:40 +msgid "Usage: alsamixer [options]" +msgstr "" + +#: alsamixer/cli.c:41 +msgid "" +"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" +msgstr "" + +#: alsamixer/cli.c:46 +msgid "" +"Debugging options:\n" +" -g, --no-color toggle using of colors\n" +" -a, --abstraction=NAME mixer abstraction level: none/basic" +msgstr "" + +#: alsamixer/cli.c:77 +#, c-format +msgid "invalid card index: %s\n" +msgstr "" + +#: alsamixer/cli.c:103 +#, c-format +msgid "unknown abstraction level: %s\n" +msgstr "" + +#: alsamixer/cli.c:108 +#, c-format +msgid "unknown option: %c\n" +msgstr "未知のオプション '%c'\n" + +#: alsamixer/cli.c:110 +msgid "try `alsamixer --help' for more information\n" +msgstr "より詳しい情報は「alsamixer --help」を実行してください\n" + +#: alsamixer/device_name.c:177 +msgid "Device name:" +msgstr "" + +#: alsamixer/die.c:37 +#, c-format +msgid "%s: %s\n" +msgstr "" + +#: alsamixer/mixer_display.c:95 +msgid "Card:" +msgstr "" + +#: alsamixer/mixer_display.c:96 +msgid "Chip:" +msgstr "" + +#: alsamixer/mixer_display.c:97 +msgid "View:" +msgstr "" + +#: alsamixer/mixer_display.c:98 +msgid "Item:" +msgstr "" + +#: alsamixer/mixer_display.c:101 +msgid "F1: Help" +msgstr "F1: ヘルプ" + +#: alsamixer/mixer_display.c:102 +msgid "F2: System information" +msgstr "" + +#: alsamixer/mixer_display.c:103 +msgid "F6: Select sound card" +msgstr "" + +#: alsamixer/mixer_display.c:104 +msgid "Esc: Exit" +msgstr "" + +#: alsamixer/mixer_display.c:171 +msgid "(unplugged)" +msgstr "" + +#: alsamixer/mixer_display.c:189 +msgid "Playback" +msgstr "" + +#: alsamixer/mixer_display.c:190 +msgid "Capture" +msgstr "" + +#: alsamixer/mixer_display.c:191 +msgid "All" +msgstr "" + +#: alsamixer/mixer_display.c:231 +msgid "mute" +msgstr "" + +#: alsamixer/mixer_display.c:272 alsamixer/mixer_display.c:282 +msgid "dB gain:" +msgstr "" + +#: alsamixer/mixer_display.c:282 +#, c-format +msgid " [%s %s, %s]" +msgstr "" + +#: alsamixer/mixer_display.c:291 alsamixer/mixer_display.c:297 +#: alsamixer/mixer_display.c:303 alsamixer/mixer_display.c:309 +msgid "Off" +msgstr "" + +#: alsamixer/mixer_display.c:297 alsamixer/mixer_display.c:309 +msgid "On" +msgstr "" + +#: alsamixer/mixer_display.c:360 +msgid "The sound device was unplugged." +msgstr "" + +#: alsamixer/mixer_display.c:361 +msgid "Press F6 to select another sound card." +msgstr "" + +#: alsamixer/mixer_display.c:376 +msgid "This sound device does not have any playback controls." +msgstr "" + +#: alsamixer/mixer_display.c:378 +msgid "This sound device does not have any capture controls." +msgstr "" + +#: alsamixer/mixer_display.c:380 +msgid "This sound device does not have any controls." +msgstr "" + +#. TRANSLATORS: playback on; one character +#: alsamixer/mixer_display.c:512 alsamixer/mixer_display.c:517 +msgid "O" +msgstr "" + +#. TRANSLATORS: playback muted; one character +#: alsamixer/mixer_display.c:514 alsamixer/mixer_display.c:518 +msgid "M" +msgstr "" + +#. TRANSLATORS: "left"; no more than two characters +#: alsamixer/mixer_display.c:532 +msgid "L" +msgstr "" + +#. TRANSLATORS: "right"; no more than two characters +#: alsamixer/mixer_display.c:536 +msgid "R" +msgstr "" + +#. TRANSLATORS: no more than eight characters +#: alsamixer/mixer_display.c:538 +msgid "CAPTURE" +msgstr "" + +#: alsamixer/mixer_display.c:588 +msgid "Front" +msgstr "" + +#: alsamixer/mixer_display.c:591 +msgid "Rear" +msgstr "" + +#: alsamixer/mixer_display.c:594 speaker-test/speaker-test.c:106 +msgid "Center" +msgstr "" + +#: alsamixer/mixer_display.c:597 +msgid "Woofer" +msgstr "" + +#: alsamixer/mixer_display.c:600 +msgid "Side" +msgstr "" + +#: alsamixer/mixer_widget.c:83 alsamixer/mixer_widget.c:88 +msgid "cannot open mixer" +msgstr "" + +#: alsamixer/mixer_widget.c:94 alsamixer/mixer_widget.c:171 +msgid "cannot load mixer controls" +msgstr "" + +#: alsamixer/mixer_widget.c:161 +#, c-format +msgid "Cannot open mixer device '%s'." +msgstr "" + +#: alsamixer/mixer_widget.c:182 +msgid "Esc Exit" +msgstr "" + +#: alsamixer/mixer_widget.c:183 +msgid "F1 ? H Help" +msgstr "" + +#: alsamixer/mixer_widget.c:184 +msgid "F2 / System information" +msgstr "" + +#: alsamixer/mixer_widget.c:185 +msgid "F3 Show playback controls" +msgstr "" + +#: alsamixer/mixer_widget.c:186 +msgid "F4 Show capture controls" +msgstr "" + +#: alsamixer/mixer_widget.c:187 +msgid "F5 Show all controls" +msgstr "" + +#: alsamixer/mixer_widget.c:188 +msgid "Tab Toggle view mode (F3/F4/F5)" +msgstr "" + +#: alsamixer/mixer_widget.c:189 +msgid "F6 S Select sound card" +msgstr "" + +#: alsamixer/mixer_widget.c:190 +msgid "L Redraw screen" +msgstr "" + +#: alsamixer/mixer_widget.c:192 +msgid "Left Move to the previous control" +msgstr "" + +#: alsamixer/mixer_widget.c:193 +msgid "Right Move to the next control" +msgstr "" + +#: alsamixer/mixer_widget.c:195 +msgid "Up/Down Change volume" +msgstr "" + +#: alsamixer/mixer_widget.c:196 +msgid "+ - Change volume" +msgstr "" + +#: alsamixer/mixer_widget.c:197 +msgid "Page Up/Dn Change volume in big steps" +msgstr "" + +#: alsamixer/mixer_widget.c:198 +msgid "End Set volume to 0%" +msgstr "" + +#: alsamixer/mixer_widget.c:199 +msgid "0-9 Set volume to 0%-90%" +msgstr "" + +#: alsamixer/mixer_widget.c:200 +msgid "Q W E Increase left/both/right volumes" +msgstr "" + +#. TRANSLATORS: or Y instead of Z +#: alsamixer/mixer_widget.c:202 +msgid "Z X C Decrease left/both/right volumes" +msgstr "" + +#: alsamixer/mixer_widget.c:203 +msgid "B Balance left and right volumes" +msgstr "" + +#: alsamixer/mixer_widget.c:205 +msgid "M Toggle mute" +msgstr "" + +#. TRANSLATORS: or , . +#: alsamixer/mixer_widget.c:207 +msgid "< > Toggle left/right mute" +msgstr "" + +#: alsamixer/mixer_widget.c:209 +msgid "Space Toggle capture" +msgstr "" + +#. TRANSLATORS: or Insert Delete +#: alsamixer/mixer_widget.c:211 +msgid "; ' Toggle left/right capture" +msgstr "" + +#: alsamixer/mixer_widget.c:213 +msgid "Authors:" +msgstr "" + +#: alsamixer/mixer_widget.c:214 +msgid " Tim Janik " +msgstr "" + +#: alsamixer/mixer_widget.c:215 +msgid " Jaroslav Kysela " +msgstr "" + +#: alsamixer/mixer_widget.c:216 +msgid " Clemens Ladisch " +msgstr "" + +#: alsamixer/mixer_widget.c:218 +msgid "Help" +msgstr "" + +#: alsamixer/proc_files.c:103 +msgid "Select File" +msgstr "" + +#: alsamixer/textbox.c:52 alsamixer/textbox.c:66 +msgid "Error" +msgstr "" + +#: alsamixer/textbox.c:80 +#, c-format +msgid "Cannot open file \"%s\"." +msgstr "" + +#: aplay/aplay.c:139 msgid "raw data" msgstr "raw データ" -#: aplay/aplay.c:129 +#: aplay/aplay.c:140 msgid "VOC" msgstr "VOC" -#: aplay/aplay.c:131 +#: aplay/aplay.c:142 msgid "WAVE" msgstr "WAVE" -#: aplay/aplay.c:132 +#: aplay/aplay.c:143 msgid "Sparc Audio" msgstr "Sparc オーディオ" -#: aplay/aplay.c:153 +#: aplay/aplay.c:164 #, fuzzy, c-format msgid "" "Usage: %s [OPTION]... [FILE]...\n" @@ -49,7 +389,6 @@ msgid "" "-f, --format=FORMAT sample format (case insensitive)\n" "-r, --rate=# sample rate\n" "-d, --duration=# interrupt after # seconds\n" -"-s, --sleep-min=# min ticks to sleep\n" "-M, --mmap mmap stream\n" "-N, --nonblock nonblocking mode\n" "-F, --period-time=# distance between interrupts is # microseconds\n" @@ -62,7 +401,17 @@ msgid "" "-T, --stop-delay=# delay for automatic PCM stop is # microseconds from " "xrun\n" "-v, --verbose show PCM structure and setup (accumulative)\n" +"-V, --vumeter=TYPE enable VU meter (TYPE: mono or stereo)\n" "-I, --separate-channels one file for each channel\n" +" --disable-resample disable automatic rate resample\n" +" --disable-channels disable automatic channel conversions\n" +" --disable-format disable automatic format conversions\n" +" --disable-softvol disable software volume control (softvol)\n" +" --test-position test ring buffer position\n" +" --test-coef=#\t test coeficient for ring buffer position (default 8)\n" +" expression for validation is: coef * (buffer_size / " +"2)\n" +" --test-nowait do not wait for ring buffer - eats whole CPU\n" msgstr "" "使用法: %s [オプション]... [ファイル]...\n" "\n" @@ -77,7 +426,6 @@ msgstr "" "-f, --format=FORMAT サンプルフォーマット (大/小文字区別)\n" "-r, --rate=# サンプルレート\n" "-d, --duration=# 指定の秒数後に終了\n" -"-s, --sleep-min=# sleep する最少 tick 数\n" "-M, --mmap mmap ストリーム\n" "-N, --nonblock 非ブロックモード\n" "-F, --period-time=# 割り込み間隔をμ秒で指定\n" @@ -89,14 +437,24 @@ msgstr "" " (0 以下の場合はバッファサイズより)\n" "-T, --stop-delay=# XRUN から指定のμ秒後に PCM の自動停止\n" "-v, --verbose PCM の設定を表示 (複数指定可能)\n" +"-V, --vumeter=TYPE enable VU meter (TYPE: mono or stereo)\n" "-I, --separate-channels 各チャネルに一つのファイルを用いる\n" +" --disable-resample disable automatic rate resample\n" +" --disable-channels disable automatic channel conversions\n" +" --disable-format disable automatic format conversions\n" +" --disable-softvol disable software volume control (softvol)\n" +" --test-position test ring buffer position\n" +" --test-coef=#\t test coeficient for ring buffer position (default 8)\n" +" expression for validation is: coef * (buffer_size / " +"2)\n" +" --test-nowait do not wait for ring buffer - eats whole CPU\n" -#: aplay/aplay.c:180 speaker-test/speaker-test.c:725 +#: aplay/aplay.c:199 speaker-test/speaker-test.c:740 #, c-format msgid "Recognized sample formats are:" msgstr "認識されるサンプルフォーマット:" -#: aplay/aplay.c:186 +#: aplay/aplay.c:205 #, c-format msgid "" "\n" @@ -105,143 +463,154 @@ msgstr "" "\n" "これらのいくつかは指定のハードウェアで使用不可能な場合があります\n" -#: aplay/aplay.c:187 +#: aplay/aplay.c:206 #, c-format msgid "The availabled format shortcuts are:\n" msgstr "可能なフォーマットの省略形:\n" -#: aplay/aplay.c:188 +#: aplay/aplay.c:207 #, c-format msgid "-f cd (16 bit little endian, 44100, stereo)\n" msgstr "-f cd (16 ビット、リトルエンディアン、44100、ステレオ)\n" -#: aplay/aplay.c:189 +#: aplay/aplay.c:208 #, c-format msgid "-f cdr (16 bit big endian, 44100, stereo)\n" msgstr "-f cdr (16 ビット、ビッグエンディアン、44100、ステレオ)\n" -#: aplay/aplay.c:190 +#: aplay/aplay.c:209 #, c-format msgid "-f dat (16 bit little endian, 48000, stereo)\n" msgstr "-f dat (16 ビット、リトルエンディアン、48000、ステレオ)\n" -#: aplay/aplay.c:204 +#: aplay/aplay.c:223 msgid "no soundcards found..." msgstr "サウンドカードが見つかりません..." -#: aplay/aplay.c:207 +#: aplay/aplay.c:226 #, c-format msgid "**** List of %s Hardware Devices ****\n" msgstr "**** ハードウェアデバイス %s のリスト ****\n" -#: aplay/aplay.c:236 +#: aplay/aplay.c:255 #, c-format msgid "card %i: %s [%s], device %i: %s [%s]\n" msgstr "カード %i: %s [%s], デバイス %i: %s [%s]\n" -#: aplay/aplay.c:242 +#: aplay/aplay.c:261 #, c-format msgid " Subdevices: %i/%i\n" msgstr " サブデバイス: %i/%i\n" -#: aplay/aplay.c:249 +#: aplay/aplay.c:268 #, c-format msgid " Subdevice #%i: %s\n" msgstr " サブデバイス #%i: %s\n" -#: aplay/aplay.c:306 +#: aplay/aplay.c:332 #, c-format msgid "Aborted by signal %s...\n" msgstr "シグナル %s で中断...\n" -#: aplay/aplay.c:390 +#: aplay/aplay.c:430 msgid "command should be named either arecord or aplay" msgstr "arecord または aplay コマンドのみ可能" -#: aplay/aplay.c:429 +#: aplay/aplay.c:469 #, c-format msgid "unrecognized file format %s" msgstr "不正なファイルフォーマット %s" -#: aplay/aplay.c:436 +#: aplay/aplay.c:476 #, c-format msgid "value %i for channels is invalid" msgstr "不正なチャネル数 %i" -#: aplay/aplay.c:455 +#: aplay/aplay.c:495 #, c-format msgid "wrong extended format '%s'" msgstr "不正な拡張フォーマット '%s'" -#: aplay/aplay.c:466 +#: aplay/aplay.c:506 #, c-format msgid "bad speed value %i" msgstr "不正なレート値 %i" -#: aplay/aplay.c:522 +#: aplay/aplay.c:592 #, c-format msgid "Try `%s --help' for more information.\n" msgstr "より詳しい情報は「%s --help」を実行してください\n" -#: aplay/aplay.c:538 +#: aplay/aplay.c:608 #, c-format msgid "audio open error: %s" msgstr "" -#: aplay/aplay.c:543 +#: aplay/aplay.c:613 #, c-format msgid "info error: %s" msgstr "" -#: aplay/aplay.c:550 +#: aplay/aplay.c:620 #, c-format msgid "nonblock setting error: %s" msgstr "" -#: aplay/aplay.c:560 aplay/aplay.c:667 aplay/aplay.c:1018 +#: aplay/aplay.c:630 aplay/aplay.c:737 aplay/aplay.c:1092 msgid "not enough memory" msgstr "メモリが足りません" -#: aplay/aplay.c:657 +#: aplay/aplay.c:727 #, c-format msgid "read error (called from line %i)" msgstr "リードエラー (%i 行)" -#: aplay/aplay.c:715 +#: aplay/aplay.c:785 #, c-format msgid "unknown length of 'fmt ' chunk (read %u, should be %u at least)" msgstr "" -#: aplay/aplay.c:723 -msgid "can't play not PCM-coded WAVE-files" -msgstr "PCM 以外の WAVE ファイルは再生できません" +#: aplay/aplay.c:795 +#, c-format +msgid "" +"unknown length of extensible 'fmt ' chunk (read %u, should be %u at least)" +msgstr "" -#: aplay/aplay.c:727 +#: aplay/aplay.c:800 +msgid "wrong format tag in extensible 'fmt ' chunk" +msgstr "" + +#: aplay/aplay.c:807 +#, c-format +msgid "can't play WAVE-file format 0x%04x which is not PCM or FLOAT encoded" +msgstr "" + +#: aplay/aplay.c:811 #, c-format msgid "can't play WAVE-files with %d tracks" msgstr "%d トラックを含む WAVE ファイルは再生できません" -#: aplay/aplay.c:735 aplay/aplay.c:832 +#: aplay/aplay.c:819 aplay/aplay.c:919 #, c-format msgid "Warning: format is changed to U8\n" msgstr "警告: フォーマットは U8 に変更されます\n" -#: aplay/aplay.c:741 +#: aplay/aplay.c:825 #, c-format msgid "Warning: format is changed to S16_LE\n" msgstr "警告: フォーマットは S16_LE に変更されます\n" -#: aplay/aplay.c:749 +#: aplay/aplay.c:833 #, c-format msgid "Warning: format is changed to S24_3LE\n" msgstr "警告: フォーマットは S24_3LE に変更されます\n" -#: aplay/aplay.c:755 +#: aplay/aplay.c:839 #, c-format msgid "Warning: format is changed to S24_LE\n" msgstr "警告: フォーマットは S24_LE に変更されます\n" -#: aplay/aplay.c:759 +#: aplay/aplay.c:843 #, c-format msgid "" " can't play WAVE-files with sample %d bits in %d bytes wide (%d channels)" @@ -249,258 +618,261 @@ msgstr "" "%2$d バイト長 %1$d サンプルビット (%3$d チャネル) の WAVE ファイルは再生でき" "ません" -#: aplay/aplay.c:768 +#: aplay/aplay.c:855 #, c-format msgid " can't play WAVE-files with sample %d bits wide" msgstr "%d ビット長のサンプルの WAVE ファイルは再生できません" -#: aplay/aplay.c:826 +#: aplay/aplay.c:913 #, c-format msgid "Warning: format is changed to MU_LAW\n" msgstr "警告: フォーマットは MU_LAW に変更されます\n" -#: aplay/aplay.c:838 +#: aplay/aplay.c:925 #, c-format msgid "Warning: format is changed to S16_BE\n" msgstr "警告: フォーマットは S16_BE に変更されます\n" -#: aplay/aplay.c:851 aplay/aplay.c:1488 aplay/aplay.c:1495 aplay/aplay.c:2011 -#: aplay/aplay.c:2023 +#: aplay/aplay.c:938 aplay/aplay.c:1768 aplay/aplay.c:1775 aplay/aplay.c:2297 +#: aplay/aplay.c:2309 msgid "read error" msgstr "リードエラー" -#: aplay/aplay.c:871 +#: aplay/aplay.c:957 msgid "Broken configuration for this PCM: no configurations available" msgstr "指定の PCM を使用できません: 設定がありません" -#: aplay/aplay.c:888 +#: aplay/aplay.c:974 msgid "Access type not available" msgstr "アクセスタイプが使用不可能" -#: aplay/aplay.c:893 +#: aplay/aplay.c:979 msgid "Sample format non available" msgstr "サンプルフォーマットが使用不可能" -#: aplay/aplay.c:898 +#: aplay/aplay.c:984 msgid "Channels count non available" msgstr "チャネル数が使用不可能" -#: aplay/aplay.c:913 +#: aplay/aplay.c:999 #, c-format msgid "Warning: rate is not accurate (requested = %iHz, got = %iHz)\n" msgstr "警告: レートが不正確です (要求値 = %iHz, 使用値 = %iHz)\n" -#: aplay/aplay.c:919 +#: aplay/aplay.c:1005 #, c-format msgid " please, try the plug plugin %s\n" msgstr " plug プラグイン%s を使用してください\n" -#: aplay/aplay.c:954 +#: aplay/aplay.c:1041 msgid "Unable to install hw params:" msgstr "hw params のインストールに失敗しました:" -#: aplay/aplay.c:961 +#: aplay/aplay.c:1048 #, c-format msgid "Can't use period equal to buffer size (%lu == %lu)" msgstr "period と buffer サイズには同じ値を使用できません (%lu == %lu)" -#: aplay/aplay.c:968 -msgid "Unable to obtain xfer align\n" -msgstr "xfer align 値を得ることができません\n" - -#: aplay/aplay.c:1005 +#: aplay/aplay.c:1079 msgid "unable to install sw params:" msgstr "sw params のインストールに失敗しました:" -#: aplay/aplay.c:1044 +#: aplay/aplay.c:1154 #, c-format msgid "status error: %s" msgstr "ステータスエラー: %s" -#: aplay/aplay.c:1052 +#: aplay/aplay.c:1164 aplay/aplay.c:1175 #, c-format msgid "%s!!! (at least %.3f ms long)\n" msgstr "%s!!! (少なくとも %.3f ms)\n" -#: aplay/aplay.c:1053 +#: aplay/aplay.c:1165 aplay/aplay.c:1168 aplay/aplay.c:1176 msgid "underrun" msgstr "アンダーラン" -#: aplay/aplay.c:1053 +#: aplay/aplay.c:1165 aplay/aplay.c:1176 msgid "overrun" msgstr "オーバーラン" -#: aplay/aplay.c:1056 +#: aplay/aplay.c:1180 #, c-format msgid "Status:\n" msgstr "ステータス:\n" -#: aplay/aplay.c:1060 +#: aplay/aplay.c:1184 #, c-format msgid "xrun: prepare error: %s" msgstr "" -#: aplay/aplay.c:1066 +#: aplay/aplay.c:1190 #, c-format msgid "Status(DRAINING):\n" msgstr "ステータス(DRAINING):\n" -#: aplay/aplay.c:1070 +#: aplay/aplay.c:1194 #, c-format msgid "capture stream format change? attempting recover...\n" msgstr "録音ストリームのフォーマットが変更? 修復を試みます...\n" -#: aplay/aplay.c:1072 +#: aplay/aplay.c:1196 #, c-format msgid "xrun(DRAINING): prepare error: %s" msgstr "" -#: aplay/aplay.c:1079 +#: aplay/aplay.c:1203 #, c-format msgid "Status(R/W):\n" msgstr "ステータス(R/W):\n" -#: aplay/aplay.c:1082 +#: aplay/aplay.c:1206 #, c-format msgid "read/write error, state = %s" msgstr "読み書きエラー, ステータス = %s" -#: aplay/aplay.c:1092 +#: aplay/aplay.c:1216 #, c-format msgid "Suspended. Trying resume. " msgstr "サスペンド中です。レジュームします。" -#: aplay/aplay.c:1097 +#: aplay/aplay.c:1221 #, c-format msgid "Failed. Restarting stream. " msgstr "失敗しました。ストリームを再スタートします。" -#: aplay/aplay.c:1099 +#: aplay/aplay.c:1223 #, c-format msgid "suspend: prepare error: %s" msgstr "サスペンド: prepare エラー: %s" -#: aplay/aplay.c:1104 +#: aplay/aplay.c:1228 #, c-format msgid "Done.\n" msgstr "終了\n" -#: aplay/aplay.c:1183 -#, c-format -msgid "Unsupported bit size %d.\n" -msgstr "" - -#: aplay/aplay.c:1219 +#: aplay/aplay.c:1250 #, c-format msgid " !clip " msgstr "" -#: aplay/aplay.c:1224 +#: aplay/aplay.c:1397 +#, c-format +msgid "Unsupported bit size %d.\n" +msgstr "" + +#: aplay/aplay.c:1431 #, c-format msgid "Max peak (%li samples): 0x%08x " msgstr "最大ピーク (%li サンプル): 0x%08x " -#: aplay/aplay.c:1258 +#: aplay/aplay.c:1465 +#, c-format +msgid "" +"Suspicious buffer position (%li total): avail = %li, delay = %li, buffer = %" +"li\n" +msgstr "" + +#: aplay/aplay.c:1528 #, c-format msgid "write error: %s" msgstr "書込エラー: %s" -#: aplay/aplay.c:1300 +#: aplay/aplay.c:1574 #, c-format msgid "writev error: %s" msgstr "書込(writev)エラー: %s" -#: aplay/aplay.c:1339 +#: aplay/aplay.c:1617 #, c-format msgid "read error: %s" msgstr "読込エラー: %s" -#: aplay/aplay.c:1378 +#: aplay/aplay.c:1660 #, c-format msgid "readv error: %s" msgstr "読込(readv)エラー: %s" -#: aplay/aplay.c:1426 +#: aplay/aplay.c:1708 msgid "can't allocate buffer for silence" msgstr "サイレンス用のバッファの取得に失敗しました" -#: aplay/aplay.c:1435 aplay/aplay.c:1663 aplay/aplay.c:1668 aplay/aplay.c:1715 -#: aplay/aplay.c:1724 aplay/aplay.c:1731 aplay/aplay.c:1741 aplay/aplay.c:1747 -#: aplay/aplay.c:1815 aplay/aplay.c:1845 aplay/aplay.c:1859 +#: aplay/aplay.c:1717 aplay/aplay.c:1943 aplay/aplay.c:1948 aplay/aplay.c:1995 +#: aplay/aplay.c:2004 aplay/aplay.c:2011 aplay/aplay.c:2021 aplay/aplay.c:2027 +#: aplay/aplay.c:2099 aplay/aplay.c:2129 aplay/aplay.c:2143 msgid "write error" msgstr "書込エラー" -#: aplay/aplay.c:1449 +#: aplay/aplay.c:1730 #, c-format msgid "voc_pcm_flush - silence error" msgstr "" -#: aplay/aplay.c:1455 +#: aplay/aplay.c:1733 msgid "voc_pcm_flush error" msgstr "" -#: aplay/aplay.c:1479 +#: aplay/aplay.c:1759 msgid "malloc error" msgstr "malloc エラー" -#: aplay/aplay.c:1483 +#: aplay/aplay.c:1763 #, c-format msgid "Playing Creative Labs Channel file '%s'...\n" msgstr "Creative Labs Channel ファイル '%s' を演奏中...\n" -#: aplay/aplay.c:1551 aplay/aplay.c:1643 +#: aplay/aplay.c:1831 aplay/aplay.c:1923 msgid "can't play packed .voc files" msgstr "packed .voc ファイルは演奏できません" -#: aplay/aplay.c:1603 +#: aplay/aplay.c:1883 #, c-format msgid "can't play loops; %s isn't seekable\n" msgstr "ループ演奏できません。%s はシーク不可能です\n" -#: aplay/aplay.c:1652 +#: aplay/aplay.c:1932 #, c-format msgid "unknown blocktype %d. terminate." msgstr "未知のブロックタイプ %d: 終了します。" -#: aplay/aplay.c:1782 +#: aplay/aplay.c:2063 #, c-format msgid "Wave doesn't support %s format..." msgstr "WAVE は %s フォーマットをサポートしません..." -#: aplay/aplay.c:1839 +#: aplay/aplay.c:2123 #, c-format msgid "Sparc Audio doesn't support %s format..." msgstr "Sparc オーディオは %s フォーマットをサポートしません..." -#: aplay/aplay.c:1920 +#: aplay/aplay.c:2204 msgid "Playing" msgstr "再生中" -#: aplay/aplay.c:1920 +#: aplay/aplay.c:2204 msgid "Recording" msgstr "録音中" -#: aplay/aplay.c:1924 +#: aplay/aplay.c:2208 #, c-format msgid "Rate %d Hz, " msgstr "レート %d Hz, " -#: aplay/aplay.c:1926 +#: aplay/aplay.c:2210 #, c-format msgid "Mono" msgstr "モノラル" -#: aplay/aplay.c:1928 +#: aplay/aplay.c:2212 #, c-format msgid "Stereo" msgstr "ステレオ" -#: aplay/aplay.c:1930 +#: aplay/aplay.c:2214 #, c-format msgid "Channels %i" msgstr "チャネル数 %i" -#: aplay/aplay.c:2285 aplay/aplay.c:2338 +#: aplay/aplay.c:2573 aplay/aplay.c:2626 #, c-format msgid "You need to specify %d files" msgstr "%d 個のファイルを指定してください" @@ -631,7 +1003,7 @@ msgstr "クライアント情報を取得できません\n" msgid "invalid sender address %s\n" msgstr "送信アドレスが不正です: %s\n" -#: seq/aconnect/aconnect.c:373 seq/aseqnet/aseqnet.c:289 +#: seq/aconnect/aconnect.c:373 seq/aseqnet/aseqnet.c:290 #, c-format msgid "invalid destination address %s\n" msgstr "受信アドレスが不正です: %s\n" @@ -656,391 +1028,382 @@ msgstr "既に接続されています\n" msgid "Connection failed (%s)\n" msgstr "接続に失敗 (%s)\n" -#: seq/aseqnet/aseqnet.c:163 +#: seq/aseqnet/aseqnet.c:164 #, c-format msgid "aseqnet - network client/server on ALSA sequencer\n" msgstr "aseqnet - ALSA sequencer 上のネットワーククライアント/サーバ\n" -#: seq/aseqnet/aseqnet.c:164 +#: seq/aseqnet/aseqnet.c:165 #, c-format msgid " Copyright (C) 1999 Takashi Iwai\n" msgstr "" -#: seq/aseqnet/aseqnet.c:165 +#: seq/aseqnet/aseqnet.c:166 #, c-format msgid "usage:\n" msgstr "使用法:\n" -#: seq/aseqnet/aseqnet.c:166 +#: seq/aseqnet/aseqnet.c:167 #, c-format msgid " server mode: aseqnet [-options]\n" msgstr " サーバモード: aseqnet [-オプション]\n" -#: seq/aseqnet/aseqnet.c:167 +#: seq/aseqnet/aseqnet.c:168 #, c-format msgid " client mode: aseqnet [-options] server_host\n" msgstr " クライアントモード: aseqnet [-オプション] サーバホスト\n" -#: seq/aseqnet/aseqnet.c:168 +#: seq/aseqnet/aseqnet.c:169 #, c-format msgid "options:\n" msgstr "オプション:\n" -#: seq/aseqnet/aseqnet.c:169 +#: seq/aseqnet/aseqnet.c:170 #, c-format msgid " -p,--port # : sepcify TCP port (digit or service name)\n" msgstr " -p,--port # : TCP ポートの指定 (数字またはサービス名)\n" -#: seq/aseqnet/aseqnet.c:170 +#: seq/aseqnet/aseqnet.c:171 #, c-format msgid " -s,--source addr : read from given addr (client:port)\n" msgstr " -s,--source addr : 指定のアドレス(クライアント:ポート)から読み込む\n" -#: seq/aseqnet/aseqnet.c:171 +#: seq/aseqnet/aseqnet.c:172 #, c-format msgid " -d,--dest addr : write to given addr (client:port)\n" msgstr " -d,--dest addr : 指定のアドレス(クライアント:ポート)に書き込む\n" -#: seq/aseqnet/aseqnet.c:172 +#: seq/aseqnet/aseqnet.c:173 #, c-format msgid " -v, --verbose : print verbose messages\n" msgstr " -v,--verbose : 冗長メッセージ表示\n" -#: seq/aseqnet/aseqnet.c:173 +#: seq/aseqnet/aseqnet.c:174 #, c-format msgid " -i, --info : print certain received events\n" msgstr " -i,--info : 受信イベントを表示する\n" -#: seq/aseqnet/aseqnet.c:187 +#: seq/aseqnet/aseqnet.c:188 #, c-format msgid "can't malloc\n" msgstr "malloc できません\n" -#: seq/aseqnet/aseqnet.c:212 +#: seq/aseqnet/aseqnet.c:213 #, c-format msgid "closing files..\n" msgstr "ファイルを閉じます..\n" -#: seq/aseqnet/aseqnet.c:271 +#: seq/aseqnet/aseqnet.c:272 #, c-format msgid "sequencer opened: %d:%d\n" msgstr "" -#: seq/aseqnet/aseqnet.c:278 +#: seq/aseqnet/aseqnet.c:279 #, c-format msgid "invalid source address %s\n" msgstr "不正な送信アドレス %s\n" -#: seq/aseqnet/aseqnet.c:308 +#: seq/aseqnet/aseqnet.c:309 #, c-format msgid "service '%s' is not found in /etc/services\n" msgstr "サービス '%s' が /etc/services に見つかりません\n" -#: seq/aseqnet/aseqnet.c:376 +#: seq/aseqnet/aseqnet.c:377 #, c-format msgid "too many connections!\n" msgstr "接続が多すぎます!\n" -#: seq/aseqnet/aseqnet.c:387 +#: seq/aseqnet/aseqnet.c:388 #, c-format msgid "accepted[%d]\n" msgstr "了解[%d]\n" -#: seq/aseqnet/aseqnet.c:410 +#: seq/aseqnet/aseqnet.c:411 #, c-format msgid "can't get address %s\n" msgstr "アドレス %s を取得できません\n" -#: seq/aseqnet/aseqnet.c:421 +#: seq/aseqnet/aseqnet.c:422 #, c-format msgid "ok.. connected\n" msgstr "ok.. 接続\n" -#: seq/aseqnet/aseqnet.c:517 +#: seq/aseqnet/aseqnet.c:518 #, c-format msgid "Channel %2d: Control event : %5d\n" msgstr "チャネル %2d: コントロール : %5d\n" -#: seq/aseqnet/aseqnet.c:521 +#: seq/aseqnet/aseqnet.c:522 #, c-format msgid "Channel %2d: Pitchbender : %5d\n" msgstr "チャネル %2d: ピッチベンド : %5d\n" -#: seq/aseqnet/aseqnet.c:525 +#: seq/aseqnet/aseqnet.c:526 #, c-format msgid "Channel %2d: Note On event : %5d\n" msgstr "チャネル %2d: ノートオン : %5d\n" -#: seq/aseqnet/aseqnet.c:529 +#: seq/aseqnet/aseqnet.c:530 #, c-format msgid "Channel %2d: Note Off event: %5d\n" msgstr "チャネル %2d: ノートオフ : %5d\n" -#: seq/aseqnet/aseqnet.c:584 +#: seq/aseqnet/aseqnet.c:585 #, c-format msgid "disconnected\n" msgstr "切り離し\n" -#: speaker-test/speaker-test.c:87 +#: speaker-test/speaker-test.c:102 msgid "Front Left" msgstr "" -#: speaker-test/speaker-test.c:88 +#: speaker-test/speaker-test.c:103 msgid "Front Right" msgstr "" -#: speaker-test/speaker-test.c:89 +#: speaker-test/speaker-test.c:104 msgid "Rear Left" msgstr "" -#: speaker-test/speaker-test.c:90 +#: speaker-test/speaker-test.c:105 msgid "Rear Right" msgstr "" -#: speaker-test/speaker-test.c:91 -msgid "Center" -msgstr "" - -#: speaker-test/speaker-test.c:92 +#: speaker-test/speaker-test.c:107 msgid "LFE" msgstr "" -#: speaker-test/speaker-test.c:93 +#: speaker-test/speaker-test.c:108 msgid "Side Left" msgstr "" -#: speaker-test/speaker-test.c:94 +#: speaker-test/speaker-test.c:109 msgid "Side Right" msgstr "" -#: speaker-test/speaker-test.c:95 +#: speaker-test/speaker-test.c:110 msgid "Channel 9" msgstr "" -#: speaker-test/speaker-test.c:96 +#: speaker-test/speaker-test.c:111 msgid "Channel 10" msgstr "" -#: speaker-test/speaker-test.c:97 +#: speaker-test/speaker-test.c:112 msgid "Channel 11" msgstr "" -#: speaker-test/speaker-test.c:98 +#: speaker-test/speaker-test.c:113 msgid "Channel 12" msgstr "" -#: speaker-test/speaker-test.c:99 +#: speaker-test/speaker-test.c:114 msgid "Channel 13" msgstr "" -#: speaker-test/speaker-test.c:100 +#: speaker-test/speaker-test.c:115 msgid "Channel 14" msgstr "" -#: speaker-test/speaker-test.c:101 +#: speaker-test/speaker-test.c:116 msgid "Channel 15" msgstr "" -#: speaker-test/speaker-test.c:102 +#: speaker-test/speaker-test.c:117 msgid "Channel 16" msgstr "" -#: speaker-test/speaker-test.c:279 +#: speaker-test/speaker-test.c:307 #, c-format msgid "Broken configuration for playback: no configurations available: %s\n" msgstr "再生用に設定できません: 設定がみつかりません: %s\n" -#: speaker-test/speaker-test.c:286 +#: speaker-test/speaker-test.c:314 #, c-format msgid "Access type not available for playback: %s\n" msgstr "アクセスタイプが不正です: %s\n" -#: speaker-test/speaker-test.c:293 +#: speaker-test/speaker-test.c:321 #, c-format msgid "Sample format not available for playback: %s\n" msgstr "指定のサンプルフォーマットを使用できません: %s\n" -#: speaker-test/speaker-test.c:300 +#: speaker-test/speaker-test.c:328 #, c-format msgid "Channels count (%i) not available for playbacks: %s\n" msgstr "チャネル数 (%i) を使用できません: %s\n" -#: speaker-test/speaker-test.c:308 +#: speaker-test/speaker-test.c:336 #, c-format msgid "Rate %iHz not available for playback: %s\n" msgstr "レート %iHz を使用できません: %s\n" -#: speaker-test/speaker-test.c:313 +#: speaker-test/speaker-test.c:341 #, c-format msgid "Rate doesn't match (requested %iHz, get %iHz, err %d)\n" msgstr "設定レートが一致しません< (要求値 %iHz, 取得値 %iHz, エラー %d)\n" -#: speaker-test/speaker-test.c:317 +#: speaker-test/speaker-test.c:345 #, c-format msgid "Rate set to %iHz (requested %iHz)\n" msgstr "レート %iHz (要求値 %iHz)\n" -#: speaker-test/speaker-test.c:323 +#: speaker-test/speaker-test.c:351 #, c-format msgid "Buffer size range from %lu to %lu\n" msgstr "バッファサイズ範囲 %lu 〜 %lu\n" -#: speaker-test/speaker-test.c:324 +#: speaker-test/speaker-test.c:352 #, c-format msgid "Period size range from %lu to %lu\n" msgstr "ピリオドサイズ範囲 %lu 〜 %lu\n" -#: speaker-test/speaker-test.c:326 +#: speaker-test/speaker-test.c:354 #, c-format msgid "Requested period time %u us\n" msgstr "要求されたピリオド長 %u us\n" -#: speaker-test/speaker-test.c:329 +#: speaker-test/speaker-test.c:357 #, c-format msgid "Unable to set period time %u us for playback: %s\n" msgstr "ピリオド長 %u us を設定できません: %s\n" -#: speaker-test/speaker-test.c:335 +#: speaker-test/speaker-test.c:363 #, c-format msgid "Requested buffer time %u us\n" msgstr "要求されたバッファ長 %u us\n" -#: speaker-test/speaker-test.c:338 +#: speaker-test/speaker-test.c:366 #, c-format msgid "Unable to set buffer time %u us for playback: %s\n" msgstr "バッファ長 %u us を設定できません: %s\n" -#: speaker-test/speaker-test.c:347 +#: speaker-test/speaker-test.c:375 #, c-format msgid "Using max buffer size %lu\n" msgstr "最大バッファサイズ %lu を使用\n" -#: speaker-test/speaker-test.c:350 +#: speaker-test/speaker-test.c:378 #, c-format msgid "Unable to set buffer size %lu for playback: %s\n" msgstr "バッファサイズ %lu を設定できません: %s\n" -#: speaker-test/speaker-test.c:356 +#: speaker-test/speaker-test.c:384 #, c-format msgid "Periods = %u\n" msgstr "ピリオド数 = %u\n" -#: speaker-test/speaker-test.c:359 +#: speaker-test/speaker-test.c:387 #, c-format msgid "Unable to set nperiods %u for playback: %s\n" msgstr "ピリオド数 %u を設定できません: %s\n" -#: speaker-test/speaker-test.c:366 -#, c-format -msgid "was set period_size = %lu\n" -msgstr "period_size = %lu で設定\n" - -#: speaker-test/speaker-test.c:367 -#, c-format -msgid "was set buffer_size = %lu\n" -msgstr "buffer_size = %lu で設定\n" - -#: speaker-test/speaker-test.c:369 -#, c-format -msgid "buffer to small, could not use\n" -msgstr "バッファが小さすぎます\n" - -#: speaker-test/speaker-test.c:376 +#: speaker-test/speaker-test.c:396 #, c-format msgid "Unable to set hw params for playback: %s\n" msgstr "hw params を設定できません: %s\n" -#: speaker-test/speaker-test.c:389 +#: speaker-test/speaker-test.c:402 +#, c-format +msgid "was set period_size = %lu\n" +msgstr "period_size = %lu で設定\n" + +#: speaker-test/speaker-test.c:403 +#, c-format +msgid "was set buffer_size = %lu\n" +msgstr "buffer_size = %lu で設定\n" + +#: speaker-test/speaker-test.c:405 +#, c-format +msgid "buffer to small, could not use\n" +msgstr "バッファが小さすぎます\n" + +#: speaker-test/speaker-test.c:418 #, c-format msgid "Unable to determine current swparams for playback: %s\n" msgstr "現在の swparams を取得できません: %s\n" -#: speaker-test/speaker-test.c:396 +#: speaker-test/speaker-test.c:425 #, c-format msgid "Unable to set start threshold mode for playback: %s\n" msgstr "start_threshold モードを設定できません: %s\n" -#: speaker-test/speaker-test.c:403 +#: speaker-test/speaker-test.c:432 #, c-format msgid "Unable to set avail min for playback: %s\n" msgstr "avail_min を設定できません: %s\n" -#: speaker-test/speaker-test.c:410 -#, c-format -msgid "Unable to set transfer align for playback: %s\n" -msgstr "転送 align を設定できません: %s\n" - -#: speaker-test/speaker-test.c:417 +#: speaker-test/speaker-test.c:439 #, c-format msgid "Unable to set sw params for playback: %s\n" msgstr "再生用の sw params を設定できません: %s\n" -#: speaker-test/speaker-test.c:432 +#: speaker-test/speaker-test.c:454 #, c-format msgid "Can't recovery from underrun, prepare failed: %s\n" msgstr "アンダーランから復帰失敗: %s\n" -#: speaker-test/speaker-test.c:443 +#: speaker-test/speaker-test.c:465 #, c-format msgid "Can't recovery from suspend, prepare failed: %s\n" msgstr "サスペンドから復帰失敗: %s\n" -#: speaker-test/speaker-test.c:517 speaker-test/speaker-test.c:911 +#: speaker-test/speaker-test.c:529 speaker-test/speaker-test.c:926 #, c-format msgid "No enough memory\n" msgstr "メモリが足りません\n" -#: speaker-test/speaker-test.c:522 +#: speaker-test/speaker-test.c:534 #, c-format msgid "Cannot open WAV file %s\n" msgstr "WAVファイルがオープンできません: %s\n" -#: speaker-test/speaker-test.c:526 speaker-test/speaker-test.c:555 +#: speaker-test/speaker-test.c:538 speaker-test/speaker-test.c:567 #, c-format msgid "Invalid WAV file %s\n" msgstr "不正なWAVファイルです: %s\n" -#: speaker-test/speaker-test.c:531 +#: speaker-test/speaker-test.c:543 #, c-format msgid "Not a WAV file: %s\n" msgstr "WAVファイルではありません: %s\n" -#: speaker-test/speaker-test.c:535 +#: speaker-test/speaker-test.c:547 #, c-format msgid "Unsupported WAV format %d for %s\n" msgstr "未サポートのWAVフォーマット %d: %s\n" -#: speaker-test/speaker-test.c:540 +#: speaker-test/speaker-test.c:552 #, c-format msgid "%s is not a mono stream (%d channels)\n" msgstr "%s はモノストリームではありません (%d チャネル)\n" -#: speaker-test/speaker-test.c:545 +#: speaker-test/speaker-test.c:557 #, c-format msgid "Sample rate doesn't match (%d) for %s\n" msgstr "サンプルレートが不一致です(%d): %s\n" -#: speaker-test/speaker-test.c:550 +#: speaker-test/speaker-test.c:562 #, c-format msgid "Unsupported sample format bits %d for %s\n" msgstr "未サポートのサンプルフォーマットビット %d: %s\n" -#: speaker-test/speaker-test.c:600 +#: speaker-test/speaker-test.c:612 #, c-format msgid "Undefined channel %d\n" msgstr "未定義のチャネル %d\n" -#: speaker-test/speaker-test.c:651 +#: speaker-test/speaker-test.c:663 #, c-format msgid "Write error: %d,%s\n" msgstr "書込エラー: %d,%s\n" -#: speaker-test/speaker-test.c:653 +#: speaker-test/speaker-test.c:665 #, c-format msgid "xrun_recovery failed: %d,%s\n" msgstr "xrun_recovery 失敗: %d,%s\n" -#: speaker-test/speaker-test.c:708 +#: speaker-test/speaker-test.c:723 #, c-format msgid "" "Usage: speaker-test [OPTION]... \n" @@ -1078,84 +1441,72 @@ msgstr "" "-W,--wavdir WAVファイルのあるディレクトリを指定\n" "\n" -#: speaker-test/speaker-test.c:820 +#: speaker-test/speaker-test.c:835 #, c-format msgid "Invalid number of periods %d\n" msgstr "不正なピリオド数 %d\n" -#: speaker-test/speaker-test.c:834 speaker-test/speaker-test.c:838 +#: speaker-test/speaker-test.c:849 speaker-test/speaker-test.c:853 #, c-format msgid "Invalid test type %s\n" msgstr "不正なテストタイプ %s\n" -#: speaker-test/speaker-test.c:850 +#: speaker-test/speaker-test.c:865 #, c-format msgid "Invalid parameter for -s option.\n" msgstr "-s オプションの値が不正です\n" -#: speaker-test/speaker-test.c:861 +#: speaker-test/speaker-test.c:876 #, c-format msgid "Unknown option '%c'\n" msgstr "未知のオプション '%c'\n" -#: speaker-test/speaker-test.c:875 +#: speaker-test/speaker-test.c:890 #, c-format msgid "Playback device is %s\n" msgstr "再生デバイス: %s\n" -#: speaker-test/speaker-test.c:876 +#: speaker-test/speaker-test.c:891 #, c-format msgid "Stream parameters are %iHz, %s, %i channels\n" msgstr "ストリームパラメータ: %iHz, %s, %i チャネル\n" -#: speaker-test/speaker-test.c:879 +#: speaker-test/speaker-test.c:894 #, c-format msgid "Using 16 octaves of pink noise\n" msgstr "16 オクターブのピンクノイズを使用\n" -#: speaker-test/speaker-test.c:882 +#: speaker-test/speaker-test.c:897 #, c-format msgid "Sine wave rate is %.4fHz\n" msgstr "正弦波レート: %.4fHz\n" -#: speaker-test/speaker-test.c:885 +#: speaker-test/speaker-test.c:900 #, c-format msgid "WAV file(s)\n" msgstr "WAV ファイル\n" -#: speaker-test/speaker-test.c:891 +#: speaker-test/speaker-test.c:906 #, c-format msgid "Playback open error: %d,%s\n" msgstr "再生オープンエラー: %d,%s\n" -#: speaker-test/speaker-test.c:896 +#: speaker-test/speaker-test.c:911 #, c-format msgid "Setting of hwparams failed: %s\n" msgstr "hwparams の設定に失敗: %s\n" -#: speaker-test/speaker-test.c:901 +#: speaker-test/speaker-test.c:916 #, c-format msgid "Setting of swparams failed: %s\n" msgstr "swparams の設定に失敗: %s\n" -#: speaker-test/speaker-test.c:942 speaker-test/speaker-test.c:964 +#: speaker-test/speaker-test.c:957 speaker-test/speaker-test.c:979 #, c-format msgid "Transfer failed: %s\n" msgstr "転送に失敗しました: %s\n" -#: speaker-test/speaker-test.c:952 +#: speaker-test/speaker-test.c:967 #, c-format msgid "Time per period = %lf\n" msgstr "ピリオド時間 = %lf\n" - -#~ msgid "snd_names_list error: %s" -#~ msgstr "snd_names_list エラー: %s" - -#~ msgid "PCM list:\n" -#~ msgstr "PCM リスト:\n" - -#~ msgid "Output failed: %s\n" -#~ msgstr "出力失敗: %s\n" - -#~ msgid "Pausing\n" -#~ msgstr "停止中\n"