diff options
author | Clemens Ladisch <clemens@ladisch.de> | 2009-05-25 10:26:22 +0200 |
---|---|---|
committer | Clemens Ladisch <clemens@ladisch.de> | 2009-05-25 10:26:22 +0200 |
commit | 5b6b5fd14bd5b0f6b07332657fc09b0a7255dda9 (patch) | |
tree | 9ab445378b65f81ff362076cff8ffd35947baefd /alsamixer | |
parent | bde1d198c11662b3ce10c4afd87ce67c2e50ae9a (diff) | |
download | alsa-utils-5b6b5fd14bd5b0f6b07332657fc09b0a7255dda9.tar.gz |
alsamixer: show channel names for multichannel controls
For multichannel mixer controls, add the channel name to each screen
control.
Also make some other small changes.
Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Diffstat (limited to 'alsamixer')
31 files changed, 3942 insertions, 2509 deletions
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 <timj@gtk.org> and -been furtherly improved by Jaroslav Kysela <perex@perex.cz>. -This document was provided by Paul Winkler <zarmzarm@erols.com>. 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,29 +12,25 @@ soundcard drivers. It supports multiple soundcards with multiple devices. .SH OPTIONS .TP -\fI\-h, \-help\fP +\fI\-h, \-\-help\fP Help: show available flags. .TP -\fI\-c\fP <card number or identification> +\fI\-c, \-\-card\fP <card number or identification> Select the soundcard to use, if you have more than one. Cards are numbered from 0 (the default). .TP -\fI\-D\fP <device identification> +\fI\-D, \-\-device\fP <device identification> Select the mixer device to control. .TP -\fI\-g\fP -Toggle the using of colors. - -.TP -\fI\-s\fP -Minimize the mixer window. +\fI\-V, \-\-view\fP <mode> +Select the starting view mode, either \fIplayback\fP, \fIcapture\fP or \fIall\fP. .TP -\fI\-V\fP <view mode> -Select the starting view mode, either \fIplayback\fP, \fIcapture\fP or \fIall\fP. +\fI\-g, \-\-no\-color\fP +Toggle the using of colors. .SH MIXER VIEWS @@ -148,6 +144,13 @@ 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 Quit the program with \fIALT Q\fP, or by hitting \fIESC\fP. @@ -169,6 +172,7 @@ fault. Plain old \fBxterm\fP seems to be fine. .SH AUTHOR .B alsamixer has been written by Tim Janik <timj@gtk.org> and -been further improved by Jaroslav Kysela <perex@perex.cz>. +been further improved by Jaroslav Kysela <perex@perex.cz> +and Clemens Ladisch <clemens@ladisch.de>. This manual page was provided by Paul Winkler <zarmzarm@erols.com>. 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 <timj@gtk.org> and Jaroslav Kysela <perex@perex.cz> - * - * 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 <perex@perex.cz> - * - * * ported to the latest mixer 0.9.x API (function based) - * - * Fri Jun 23 14:10:00 MEST 2000 Jaroslav Kysela <perex@perex.cz> - * - * * 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 <iwai@ww.uni-erlangen.de> - * - * * a group is split into front, rear, center and woofer elements. - * - * Mon Jan 3 23:33:42 MET 2000 Jaroslav Kysela <perex@perex.cz> - * - * * version 1.00 - * - * * ported to new mixer API (scontrol control) - * - * Sun Feb 21 19:55:01 1999 Tim Janik <timj@gtk.org> - * - * * 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 <timj@gtk.org> - * - * * 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 <carl@dreamcoat.che.uct.ac.za>. - * - * 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 <stdio.h> -#include <unistd.h> -#include <fcntl.h> -#include <sys/ioctl.h> - -#include <errno.h> - -#include <string.h> -#include <stdlib.h> -#include <unistd.h> -#include <sys/signal.h> -#include <sys/time.h> - -#include <locale.h> - -#ifndef CURSESINC -#include <ncurses.h> -#else -#include CURSESINC -#endif -#include <time.h> - -#include <alsa/asoundlib.h> -#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 <timj@gtk.org> and Jaroslav Kysela <perex@perex.cz>.\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 <card: 0...7>] [-D <mixer device>] [-g] [-s] [-V <view>] [-a <abst>]\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 <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <alsa/asoundlib.h> +#include <menu.h> +#include "gettext_curses.h" +#include "die.h" +#include "mem.h" +#include "utils.h" +#include "colors.h" +#include "widget.h" +#include "mixer_widget.h" +#include "device_name.h" +#include "card_select.h" + +struct card { + struct card *next; + char *indexstr; + char *name; + char *device_name; +}; + +static struct widget list_widget; +static struct card first_card; +static ITEM **items; +static MENU *menu; +static ITEM *initial_item; + +static void on_key_enter(void) +{ + ITEM *item = current_item(menu); + if (item) { + struct card *card = item_userptr(item); + if (card->device_name) { + if (select_card_by_name(card->device_name)) + list_widget.close(); + } else { + create_device_name_form(); + } + } +} + +static void on_menu_key(int key) +{ + static const struct { + int key; + int request; + } key_map[] = { + { KEY_DOWN, REQ_DOWN_ITEM }, + { KEY_UP, REQ_UP_ITEM }, + { KEY_HOME, REQ_FIRST_ITEM }, + { KEY_NPAGE, REQ_SCR_DPAGE }, + { KEY_PPAGE, REQ_SCR_UPAGE }, + { KEY_BEG, REQ_FIRST_ITEM }, + { KEY_END, REQ_LAST_ITEM }, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(key_map); ++i) + if (key_map[i].key == key) { + menu_driver(menu, key_map[i].request); + break; + } +} + +static void on_handle_key(int key) +{ + switch (key) { + case 27: + case KEY_CANCEL: + case 'q': + case 'Q': + list_widget.close(); + break; + case 10: + case 13: + case KEY_ENTER: + on_key_enter(); + break; + default: + on_menu_key(key); + break; + } +} + +static bool create(void) +{ + int rows, columns; + const char *title; + + if (screen_lines < 3 || screen_cols < 10) { + beep(); + list_widget.close(); + return FALSE; + } + scale_menu(menu, &rows, &columns); + rows += 2; + columns += 2; + if (rows > screen_lines) + rows = screen_lines; + if (columns > screen_cols) + columns = screen_cols; + + widget_init(&list_widget, rows, columns, SCREEN_CENTER, SCREEN_CENTER, + attr_menu, WIDGET_BORDER | WIDGET_SUBWINDOW); + + title = _("Sound Card"); + mvwprintw(list_widget.window, 0, (columns - 2 - get_mbs_width(title)) / 2, " %s ", title); + set_menu_win(menu, list_widget.window); + set_menu_sub(menu, list_widget.subwindow); + return TRUE; +} + +static void on_window_size_changed(void) +{ + unpost_menu(menu); + if (!create()) + return; + post_menu(menu); +} + +static void on_close(void) +{ + unsigned int i; + struct card *card, *next_card; + + unpost_menu(menu); + free_menu(menu); + for (i = 0; items[i]; ++i) + free_item(items[i]); + free(items); + for (card = first_card.next; card; card = next_card) { + next_card = card->next; + free(card->indexstr); + free(card->name); + free(card->device_name); + free(card); + } + widget_free(&list_widget); +} + +void close_card_select_list(void) +{ + on_close(); +} + +static struct widget list_widget = { + .handle_key = on_handle_key, + .window_size_changed = on_window_size_changed, + .close = on_close, +}; + +static int get_cards(void) +{ + int count, number, err; + snd_ctl_t *ctl; + snd_ctl_card_info_t *info; + char buf[16]; + struct card *card, *prev_card; + + first_card.indexstr = "-"; + first_card.name = _("(default)"); + first_card.device_name = "default"; + count = 1; + + snd_ctl_card_info_alloca(&info); + prev_card = &first_card; + number = -1; + for (;;) { + err = snd_card_next(&number); + if (err < 0) + fatal_alsa_error(_("cannot enumerate sound cards"), err); + if (number < 0) + break; + sprintf(buf, "hw:%d", number); + err = snd_ctl_open(&ctl, buf, 0); + if (err < 0) + continue; + err = snd_ctl_card_info(ctl, info); + snd_ctl_close(ctl); + if (err < 0) + continue; + card = ccalloc(1, sizeof *card); + sprintf(buf, "%d", number); + card->indexstr = cstrdup(buf); + card->name = cstrdup(snd_ctl_card_info_get_name(info)); + sprintf(buf, "hw:%d", number); + card->device_name = cstrdup(buf); + prev_card->next = card; + prev_card = card; + ++count; + } + + card = ccalloc(1, sizeof *card); + card->indexstr = cstrdup(" "); + card->name = cstrdup(_("enter device name...")); + prev_card->next = card; + ++count; + + return count; +} + +static void create_list_items(int cards) +{ + int i; + struct card *card; + ITEM *item; + + initial_item = NULL; + items = ccalloc(cards + 1, sizeof(ITEM*)); + i = 0; + for (card = &first_card; card; card = card->next) { + item = new_item(card->indexstr, card->name); + if (!item) + fatal_error("cannot create menu item"); + set_item_userptr(item, card); + items[i++] = item; + if (!initial_item && + mixer_device_name && + (!card->device_name || + !strcmp(card->device_name, mixer_device_name))) + initial_item = item; + } + assert(i == cards); +} + +void create_card_select_list(void) +{ + int cards; + + cards = get_cards(); + create_list_items(cards); + + menu = new_menu(items); + if (!menu) + fatal_error("cannot create menu"); + set_menu_fore(menu, attr_menu_selected); + set_menu_back(menu, attr_menu); + set_menu_mark(menu, NULL); + if (initial_item) + set_current_item(menu, initial_item); + set_menu_spacing(menu, 2, 1, 1); + menu_opts_on(menu, O_SHOWDESC); + + if (!create()) + return; + + post_menu(menu); +} 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 <timj@gtk.org> + * Jaroslav Kysela <perex@perex.cz> + * Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include <stdio.h> +#include <stdlib.h> +#include <locale.h> +#include <getopt.h> +#include <alsa/asoundlib.h> +#include "gettext_curses.h" +#include "mixer_widget.h" +#include "mainloop.h" + +static int use_color = 1; +static struct snd_mixer_selem_regopt selem_regopt = { + .ver = 1, + .abstract = SND_MIXER_SABSTRACT_NONE, + .device = "default", +}; + +static void show_help(void) +{ + puts(_("Usage: alsamixer [options]")); + puts(_("Useful options:\n" + " -h, --help this help\n" + " -c, --card=NUMBER sound card number or id\n" + " -D, --device=NAME mixer device name\n" + " -V, --view=MODE starting view mode: playback/capture/all")); + puts(_("Debugging options:\n" + " -g, --no-color toggle using of colors\n" + " -a, --abstraction=NAME mixer abstraction level: none/basic")); +} + +static void parse_options(int argc, char *argv[]) +{ + static const char short_options[] = "hc:D:V:gsa:"; + static const struct option long_options[] = { + { .name = "help", .val = 'h' }, + { .name = "card", .has_arg = 1, .val = 'c' }, + { .name = "device", .has_arg = 1, .val = 'D' }, + { .name = "view", .has_arg = 1, .val = 'V' }, + { .name = "no-color", .val = 'g' }, + { .name = "abstraction", .has_arg = 1, .val = 'a' }, + { } + }; + int option; + int card_index; + static char name_buf[16]; + + while ((option = getopt_long(argc, argv, short_options, + long_options, NULL)) != -1) { + switch (option) { + case '?': + case 'h': + show_help(); + exit(EXIT_SUCCESS); + case 'c': + card_index = snd_card_get_index(optarg); + if (card_index < 0) { + fprintf(stderr, _("invalid card index: %s\n"), optarg); + goto fail; + } + sprintf(name_buf, "hw:%d", card_index); + selem_regopt.device = name_buf; + break; + case 'D': + selem_regopt.device = optarg; + break; + case 'V': + if (*optarg == 'p' || *optarg == 'P') + view_mode = VIEW_MODE_PLAYBACK; + else if (*optarg == 'c' || *optarg == 'C') + view_mode = VIEW_MODE_CAPTURE; + else + view_mode = VIEW_MODE_ALL; + break; + case 'g': + use_color = !use_color; + break; + case 'a': + if (!strcmp(optarg, "none")) + selem_regopt.abstract = SND_MIXER_SABSTRACT_NONE; + else if (!strcmp(optarg, "basic")) + selem_regopt.abstract = SND_MIXER_SABSTRACT_BASIC; + else { + fprintf(stderr, _("unknown abstraction level: %s\n"), optarg); + goto fail; + } + break; + default: + fprintf(stderr, _("unknown option: %c\n"), option); +fail: + fputs(_("try `alsamixer --help' for more information\n"), stderr); + exit(EXIT_FAILURE); + } + } +} + +int main(int argc, char *argv[]) +{ + setlocale(LC_ALL, ""); +#ifdef ENABLE_NLS_IN_CURSES + textdomain(PACKAGE); +#endif + + parse_options(argc, argv); + + create_mixer_object(&selem_regopt); + + initialize_curses(use_color); + + create_mixer_widget(); + + mainloop(); + + shutdown(); + return 0; +} 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 <timj@gtk.org> + * Jaroslav Kysela <perex@perex.cz> + * Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include CURSESINC +#include "colors.h" + +int attr_mixer_frame; +int attr_mixer_text; +int attr_mixer_active; +int attr_ctl_frame; +int attr_ctl_mute; +int attr_ctl_nomute; +int attr_ctl_capture; +int attr_ctl_nocapture; +int attr_ctl_label; +int attr_ctl_label_focus; +int attr_ctl_mark_focus; +int attr_ctl_bar; +int attr_ctl_inactive; +int attr_ctl_label_inactive; +int attr_errormsg; +int attr_infomsg; +int attr_textbox; +int attr_textfield; +int attr_menu; +int attr_menu_selected; + +void init_colors(int use_color) +{ + if (!!has_colors() == !!use_color) { + start_color(); + + init_pair(1, COLOR_CYAN, COLOR_BLACK); + init_pair(2, COLOR_YELLOW, COLOR_BLACK); + init_pair(3, COLOR_WHITE, COLOR_GREEN); + init_pair(4, COLOR_RED, COLOR_BLACK); + init_pair(5, COLOR_WHITE, COLOR_BLACK); + init_pair(6, COLOR_WHITE, COLOR_BLUE); + init_pair(7, COLOR_RED, COLOR_BLUE); + init_pair(8, COLOR_GREEN, COLOR_GREEN); + init_pair(9, COLOR_WHITE, COLOR_RED); + + attr_mixer_frame = COLOR_PAIR(1); + attr_mixer_text = COLOR_PAIR(1); + attr_mixer_active = A_BOLD | COLOR_PAIR(2); + attr_ctl_frame = A_BOLD | COLOR_PAIR(1); + attr_ctl_mute = COLOR_PAIR(1); + attr_ctl_nomute = A_BOLD | COLOR_PAIR(3); + attr_ctl_capture = A_BOLD | COLOR_PAIR(4); + attr_ctl_nocapture = COLOR_PAIR(5); + attr_ctl_label = A_BOLD | COLOR_PAIR(6); + attr_ctl_label_focus = A_BOLD | COLOR_PAIR(7); + attr_ctl_mark_focus = A_BOLD | COLOR_PAIR(4); + attr_ctl_bar = A_BOLD | COLOR_PAIR(8); + attr_ctl_inactive = COLOR_PAIR(5); + attr_ctl_label_inactive = A_REVERSE | COLOR_PAIR(5); + attr_errormsg = A_BOLD | COLOR_PAIR(9); + attr_infomsg = A_BOLD | COLOR_PAIR(6); + attr_textbox = A_BOLD | COLOR_PAIR(6); + attr_textfield = A_REVERSE | COLOR_PAIR(5); + attr_menu = A_BOLD | COLOR_PAIR(6); + attr_menu_selected = A_REVERSE | COLOR_PAIR(6); + } else { + attr_mixer_frame = A_NORMAL; + attr_mixer_text = A_NORMAL; + attr_mixer_active = A_BOLD; + attr_ctl_frame = A_BOLD; + attr_ctl_mute = A_NORMAL; + attr_ctl_nomute = A_BOLD; + attr_ctl_capture = A_BOLD; + attr_ctl_nocapture = A_NORMAL; + attr_ctl_label = A_REVERSE; + attr_ctl_label_focus = A_REVERSE | A_BOLD; + attr_ctl_mark_focus = A_BOLD; + attr_ctl_bar = A_BOLD; + attr_ctl_inactive = A_NORMAL; + attr_ctl_label_inactive = A_REVERSE; + attr_errormsg = A_STANDOUT; + attr_infomsg = A_NORMAL; + attr_textbox = A_NORMAL; + attr_textfield = A_REVERSE; + attr_menu = A_NORMAL; + attr_menu_selected = A_REVERSE; + } +} 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 <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include <stdlib.h> +#include <string.h> +#include CURSESINC +#include <form.h> +#include "gettext_curses.h" +#include "die.h" +#include "mem.h" +#include "utils.h" +#include "colors.h" +#include "widget.h" +#include "mixer_widget.h" +#include "card_select.h" +#include "device_name.h" + +static struct widget form_widget; +static FIELD *fields[3]; +static FORM *form; + +static char *dup_current_name(void) +{ + int rows, cols, max, i; + char *s; + + if (form_driver(form, REQ_VALIDATION) == E_OK) { + dynamic_field_info(fields[1], &rows, &cols, &max); + s = ccalloc(1, cols + 1); + memcpy(s, field_buffer(fields[1], 0), cols); + for (i = strlen(s) - 1; i >= 0 && s[i] == ' '; --i) + s[i] = '\0'; + return s; + } else { + return cstrdup(""); + } +} + +static void on_key_enter(void) +{ + char *s; + bool ok; + + s = dup_current_name(); + ok = select_card_by_name(s); + free(s); + if (ok) { + form_widget.close(); + close_card_select_list(); + } +} + +static void on_form_key(int key) +{ + static const struct { + int key; + int request; + } key_map[] = { + { KEY_LEFT, REQ_PREV_CHAR }, + { KEY_RIGHT, REQ_NEXT_CHAR }, + { KEY_HOME, REQ_BEG_FIELD }, + { KEY_BACKSPACE, REQ_DEL_PREV }, + { KEY_DC, REQ_DEL_CHAR }, + { KEY_BEG, REQ_BEG_FIELD }, + { KEY_END, REQ_END_FIELD }, + }; + unsigned int i; + + if (key >= 32 && key < 256) { + form_driver(form, key); + return; + } + for (i = 0; i < ARRAY_SIZE(key_map); ++i) + if (key_map[i].key == key) { + form_driver(form, key_map[i].request); + break; + } +} + +static void on_handle_key(int key) +{ + switch (key) { + case 27: + case KEY_CANCEL: + form_widget.close(); + break; + case 10: + case 13: + case KEY_ENTER: + on_key_enter(); + break; + default: + on_form_key(key); + break; + } +} + +static bool create(void) +{ + const char *title; + + if (screen_lines < 6 || screen_cols < 36) { + form_widget.close(); + beep(); + return FALSE; + } + widget_init(&form_widget, + 6, 36, SCREEN_CENTER, SCREEN_CENTER, + attr_textbox, WIDGET_BORDER | WIDGET_SUBWINDOW | WIDGET_CURSOR_VISIBLE); + title = _("Sound Card"); + mvwprintw(form_widget.window, 0, (36 - 2 - get_mbs_width(title)) / 2, " %s ", title); + + set_form_win(form, form_widget.window); + set_form_sub(form, form_widget.subwindow); + return TRUE; +} + +static void on_window_size_changed(void) +{ + form_driver(form, REQ_VALIDATION); /* save field value */ + unpost_form(form); + + if (!create()) + return; + + /* + * This call fails because ncurses does not allow changing options of + * the current field, and we cannot change the current field because + * there is only one. The only way to make this work would be to throw + * away and recreate all fields. + */ + field_opts_off(fields[1], O_BLANK); + + post_form(form); +} + +static void on_close(void) +{ + unpost_form(form); + free_form(form); + free_field(fields[0]); + free_field(fields[1]); + widget_free(&form_widget); +} + +static struct widget form_widget = { + .handle_key = on_handle_key, + .window_size_changed = on_window_size_changed, + .close = on_close, +}; + +void create_device_name_form(void) +{ + fields[0] = new_field(1, 32, 1, 1, 0, 0); + if (!fields[0]) + fatal_error("cannot create field"); + field_opts_off(fields[0], O_ACTIVE); + field_opts_off(fields[0], O_EDIT); + set_field_fore(fields[0], attr_textbox); + set_field_back(fields[0], attr_textbox); + set_field_buffer(fields[0], 0, _("Device name:")); + + fields[1] = new_field(1, 32, 2, 1, 0, 0); + if (!fields[1]) + fatal_error("cannot create field"); + field_opts_off(fields[1], O_AUTOSKIP); + field_opts_off(fields[1], O_NULLOK); + field_opts_off(fields[1], O_STATIC); + set_field_fore(fields[1], attr_textfield); + set_field_back(fields[1], attr_textfield); + set_field_buffer(fields[1], 0, mixer_device_name); + + form = new_form(fields); + if (!form) + fatal_error("cannot create form"); + + if (!create()) + return; + + post_form(form); +} 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 <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include <stdio.h> +#include <stdlib.h> +#include <alsa/asoundlib.h> +#include "gettext_curses.h" +#include "mainloop.h" +#include "die.h" + +void fatal_error(const char *msg) +{ + shutdown(); + fprintf(stderr, "%s\n", msg); + exit(EXIT_FAILURE); +} + +void fatal_alsa_error(const char *msg, int err) +{ + shutdown(); + fprintf(stderr, _("%s: %s\n"), msg, snd_strerror(err)); + exit(EXIT_FAILURE); +} 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 <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <poll.h> +#include <panel.h> +#include <alsa/asoundlib.h> +#include "mem.h" +#include "die.h" +#include "colors.h" +#include "widget.h" +#include "mixer_widget.h" +#include "mixer_display.h" +#include "mainloop.h" + +static WINDOW *curses_initialized; + +static void black_hole_error_handler(const char *file, int line, + const char *function, int err, + const char *fmt, ...) +{ +} + +void initialize_curses(bool use_color) +{ + curses_initialized = initscr(); + cbreak(); + noecho(); +#ifdef NCURSES_VERSION + set_escdelay(100); +#endif + window_size_changed(); /* update screen_lines/cols */ + init_colors(use_color); + snd_lib_error_set_handler(black_hole_error_handler); +} + +void shutdown(void) +{ + if (curses_initialized) { + clear(); + refresh(); + curs_set(1); + endwin(); + } + mixer_shutdown(); +} + +void mainloop(void) +{ + struct pollfd *pollfds = NULL; + int nfds = 0, n; + struct widget *active_widget; + unsigned short revents; + int key; + int err; + + for (;;) { + update_panels(); + doupdate(); + + active_widget = get_active_widget(); + if (!active_widget) + break; + + n = 1 + snd_mixer_poll_descriptors_count(mixer); + if (n != nfds) { + free(pollfds); + nfds = n; + pollfds = ccalloc(nfds, sizeof *pollfds); + pollfds[0].fd = fileno(stdin); + pollfds[0].events = POLLIN; + } + err = snd_mixer_poll_descriptors(mixer, &pollfds[1], nfds - 1); + if (err < 0) + fatal_alsa_error("cannot get poll descriptors", err); + n = poll(pollfds, nfds, -1); + if (n < 0) { + if (errno == EINTR) { + pollfds[0].revents = 0; + doupdate(); /* handle SIGWINCH */ + } else { + fatal_error("poll error"); + } + } + if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) + break; + if (pollfds[0].revents & POLLIN) + --n; + if (n > 0) { + err = snd_mixer_poll_descriptors_revents(mixer, &pollfds[1], nfds - 1, &revents); + if (err < 0) + fatal_alsa_error("cannot get poll events", err); + if (revents & (POLLERR | POLLNVAL)) + close_mixer_device(); + else if (revents & POLLIN) + snd_mixer_handle_events(mixer); + } + key = wgetch(active_widget->window); + while (key != ERR) { +#ifdef KEY_RESIZE + if (key == KEY_RESIZE) + window_size_changed(); + else +#endif + active_widget->handle_key(key); + active_widget = get_active_widget(); + if (!active_widget) + break; + key = wgetch(active_widget->window); + } + if (!active_widget) + break; + if (controls_changed) + display_controls(); + } + free(pollfds); +} 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 <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define _GNU_SOURCE +#include "aconfig.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include "die.h" +#include "mem.h" + +static void check(void *p) +{ + if (!p) + fatal_error("out of memory"); +} + +void *ccalloc(size_t n, size_t size) +{ + void *mem = calloc(n, size); + if (n && size) + check(mem); + return mem; +} + +void *crealloc(void *ptr, size_t new_size) +{ + ptr = realloc(ptr, new_size); + if (new_size) + check(ptr); + return ptr; +} + +char *cstrdup(const char *s) +{ + char *str = strdup(s); + check(str); + return str; +} + +char *casprintf(const char *fmt, ...) +{ + va_list ap; + char *str; + + va_start(ap, fmt); + if (vasprintf(&str, fmt, ap) < 0) + check(NULL); + va_end(ap); + return str; +} 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 <stddef.h> + +void *ccalloc(size_t n, size_t size); +void *crealloc(void *ptr, size_t new_size); +char *cstrdup(const char *s); +char *casprintf(const char *fmt, ...) __attribute__((__format__(printf, 1, 2))); + +#endif 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 <timj@gtk.org> + * Jaroslav Kysela <perex@perex.cz> + * Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include CURSESINC +#include <alsa/asoundlib.h> +#include "utils.h" +#include "mem.h" +#include "mixer_display.h" +#include "mixer_widget.h" +#include "mixer_controls.h" + +struct control *controls; +unsigned int controls_count; + +static const snd_mixer_selem_channel_id_t supported_channels[] = { + SND_MIXER_SCHN_FRONT_LEFT, + SND_MIXER_SCHN_FRONT_RIGHT, + SND_MIXER_SCHN_REAR_LEFT, + SND_MIXER_SCHN_REAR_RIGHT, + SND_MIXER_SCHN_FRONT_CENTER, + SND_MIXER_SCHN_WOOFER, + SND_MIXER_SCHN_SIDE_LEFT, + SND_MIXER_SCHN_SIDE_RIGHT, +}; +#define LAST_SUPPORTED_CHANNEL SND_MIXER_SCHN_SIDE_RIGHT + +static const snd_mixer_selem_channel_id_t control_channels[][2] = { + { SND_MIXER_SCHN_FRONT_LEFT, SND_MIXER_SCHN_FRONT_RIGHT }, + { SND_MIXER_SCHN_REAR_LEFT, SND_MIXER_SCHN_REAR_RIGHT }, + { SND_MIXER_SCHN_FRONT_CENTER, SND_MIXER_SCHN_UNKNOWN }, + { SND_MIXER_SCHN_WOOFER, SND_MIXER_SCHN_UNKNOWN }, + { SND_MIXER_SCHN_SIDE_LEFT, SND_MIXER_SCHN_SIDE_RIGHT }, +}; + +bool are_there_any_controls(void) +{ + snd_mixer_elem_t *elem; + unsigned int i; + + for (elem = snd_mixer_first_elem(mixer); + elem; + elem = snd_mixer_elem_next(elem)) { + if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE) + continue; + if (snd_mixer_selem_is_enumerated(elem)) + return TRUE; + if (snd_mixer_selem_has_playback_volume_joined(elem) || + snd_mixer_selem_has_capture_volume_joined(elem) || + snd_mixer_selem_has_playback_switch_joined(elem) || + snd_mixer_selem_has_capture_switch_joined(elem)) + return TRUE; + for (i = 0; i < ARRAY_SIZE(supported_channels); ++i) + if (snd_mixer_selem_has_playback_channel(elem, supported_channels[i]) || + snd_mixer_selem_has_capture_channel(elem, supported_channels[i])) + return TRUE; + } + return FALSE; +} + +static bool has_more_than_front_capture_channels(snd_mixer_elem_t *elem) +{ + unsigned int i; + + for (i = 2; i < ARRAY_SIZE(supported_channels); ++i) + if (snd_mixer_selem_has_capture_channel(elem, supported_channels[i])) + return TRUE; + return FALSE; +} + +static bool has_any_control_channel(snd_mixer_elem_t *elem, + const snd_mixer_selem_channel_id_t channels[2], + int (*has_channel)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t)) +{ + return has_channel(elem, channels[0]) || + (channels[1] != SND_MIXER_SCHN_UNKNOWN && has_channel(elem, channels[1])); +} + +static bool has_merged_cswitch(snd_mixer_elem_t *elem) +{ + bool pvol, psw; + unsigned int i; + + pvol = snd_mixer_selem_has_playback_volume(elem); + psw = snd_mixer_selem_has_playback_switch(elem); + if ((pvol || psw) && + snd_mixer_selem_has_capture_switch(elem) && + !snd_mixer_selem_has_capture_volume(elem)) { + if (snd_mixer_selem_has_capture_switch_joined(elem)) + return TRUE; + else if (((pvol && snd_mixer_selem_has_playback_volume_joined(elem)) || + (psw && snd_mixer_selem_has_playback_switch_joined(elem))) && + has_more_than_front_capture_channels(elem)) + return FALSE; + for (i = 0; i < ARRAY_SIZE(control_channels); ++i) { + if (has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_capture_channel) && + !has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_playback_channel)) + return FALSE; + } + return TRUE; + } + return FALSE; +} + +static unsigned int get_playback_controls_count(snd_mixer_elem_t *elem) +{ + unsigned int count = 0; + unsigned int i; + int has_vol, has_sw; + + has_vol = snd_mixer_selem_has_playback_volume(elem); + has_sw = snd_mixer_selem_has_playback_switch(elem); + if (!has_vol && !has_sw) + return 0; + if ((!has_vol || snd_mixer_selem_has_playback_volume_joined(elem)) && + (!has_sw || snd_mixer_selem_has_playback_switch_joined(elem))) + return 1; + for (i = 0; i < ARRAY_SIZE(control_channels); ++i) { + if (snd_mixer_selem_has_playback_channel(elem, control_channels[i][0]) || + (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN && + snd_mixer_selem_has_playback_channel(elem, control_channels[i][1]))) + ++count; + } + return count; +} + +static unsigned int get_capture_controls_count(snd_mixer_elem_t *elem) +{ + unsigned int count = 0; + unsigned int i; + int has_vol, has_sw; + + has_vol = snd_mixer_selem_has_capture_volume(elem); + has_sw = snd_mixer_selem_has_capture_switch(elem); + if ((!has_vol && !has_sw) || + (view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem))) + return 0; + if ((!has_vol || snd_mixer_selem_has_capture_volume_joined(elem)) && + (!has_sw || snd_mixer_selem_has_capture_switch_joined(elem))) + return 1; + for (i = 0; i < ARRAY_SIZE(control_channels); ++i) { + if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0]) || + (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN && + snd_mixer_selem_has_capture_channel(elem, control_channels[i][1]))) + ++count; + } + return count; +} + +static unsigned int get_controls_count_for_elem(snd_mixer_elem_t *elem) +{ + unsigned int p, c; + + if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE) + return 0; + if (snd_mixer_selem_is_enumerated(elem)) { + switch (view_mode) { + case VIEW_MODE_PLAYBACK: + return snd_mixer_selem_is_enum_capture(elem) ? 0 : 1; + case VIEW_MODE_CAPTURE: + return snd_mixer_selem_is_enum_capture(elem) ? 1 : 0; + case VIEW_MODE_ALL: + default: + return 1; + } + } + switch (view_mode) { + case VIEW_MODE_PLAYBACK: + return get_playback_controls_count(elem); + case VIEW_MODE_CAPTURE: + return get_capture_controls_count(elem); + case VIEW_MODE_ALL: + default: + p = get_playback_controls_count(elem); + c = get_capture_controls_count(elem); + return has_merged_cswitch(elem) ? p : p + c; + } +} + +static void create_name(struct control *control) +{ + unsigned int index; + char *s; + + index = snd_mixer_selem_get_index(control->elem); + if (index > 0) + control->name = casprintf("%s %u", snd_mixer_selem_get_name(control->elem), index); + else + control->name = cstrdup(snd_mixer_selem_get_name(control->elem)); + + while ((s = strstr(control->name, "IEC958")) != NULL) + memcpy(s, "S/PDIF", 6); +} + +static unsigned int create_controls_for_elem(snd_mixer_elem_t *elem, struct control *control) +{ + unsigned int count = 0; + unsigned int i; + unsigned int multich_flag; + unsigned int enum_index; + struct control *front_control = NULL; + bool has_pvol, has_psw; + bool has_cvol, has_csw; + bool has_channel[LAST_SUPPORTED_CHANNEL + 1]; + bool merged_cswitch; + bool has_ch0, has_ch1; + + if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE) + return 0; + if (snd_mixer_selem_is_enumerated(elem)) { + if ((view_mode == VIEW_MODE_PLAYBACK && snd_mixer_selem_is_enum_capture(elem)) || + (view_mode == VIEW_MODE_CAPTURE && !snd_mixer_selem_is_enum_capture(elem))) + return 0; + control->elem = elem; + control->flags = TYPE_ENUM; + control->enum_channel_bits = 0; + for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i) + if (snd_mixer_selem_get_enum_item(control->elem, (snd_mixer_selem_channel_id_t)i, &enum_index) >= 0) + control->enum_channel_bits |= 1 << i; + if (snd_mixer_selem_is_active(control->elem)) + control->flags |= IS_ACTIVE; + create_name(control); + return 1; + } + has_pvol = snd_mixer_selem_has_playback_volume(elem); + has_psw = snd_mixer_selem_has_playback_switch(elem); + has_cvol = snd_mixer_selem_has_capture_volume(elem); + has_csw = snd_mixer_selem_has_capture_switch(elem); + merged_cswitch = view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem); + if (view_mode != VIEW_MODE_CAPTURE && (has_pvol || has_psw)) { + if ((!has_pvol || snd_mixer_selem_has_playback_volume_joined(elem)) && + (!has_psw || snd_mixer_selem_has_playback_switch_joined(elem))) { + control->elem = elem; + if (has_pvol) { + control->flags |= TYPE_PVOLUME | HAS_VOLUME_0; + control->volume_channels[0] = 0; + } + if (has_psw) { + control->flags |= TYPE_PSWITCH | HAS_PSWITCH_0; + control->pswitch_channels[0] = 0; + } + if (merged_cswitch) { + control->flags |= TYPE_CSWITCH; + if (snd_mixer_selem_has_capture_switch_joined(elem)) { + control->flags |= HAS_CSWITCH_0; + control->cswitch_channels[0] = 0; + } else { + if (snd_mixer_selem_has_capture_channel(elem, control_channels[0][0])) { + control->flags |= HAS_CSWITCH_0; + control->cswitch_channels[0] = control_channels[0][0]; + } + if (control_channels[0][1] != SND_MIXER_SCHN_UNKNOWN && + snd_mixer_selem_has_capture_channel(elem, control_channels[0][1])) { + control->flags |= HAS_CSWITCH_1; + control->cswitch_channels[1] = control_channels[0][1]; + } + } + if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) { + control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1; + control->cswitch_channels[0] = control->cswitch_channels[1]; + } + } + if (snd_mixer_selem_is_active(control->elem)) + control->flags |= IS_ACTIVE; + create_name(control); + ++control; + ++count; + } else { + multich_flag = 0; + for (i = 0; i < ARRAY_SIZE(supported_channels); ++i) + has_channel[supported_channels[i]] = + snd_mixer_selem_has_playback_channel(elem, supported_channels[i]); + for (i = 0; i < ARRAY_SIZE(control_channels); ++i) { + has_ch0 = has_channel[control_channels[i][0]]; + has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN && + has_channel[control_channels[i][1]]; + if (!has_ch0 && !has_ch1) + continue; + control->elem = elem; + if (has_pvol) { + control->flags |= TYPE_PVOLUME; + if (snd_mixer_selem_has_playback_volume_joined(elem)) { + control->flags |= HAS_VOLUME_0; + control->volume_channels[0] = 0; + } else { + if (has_ch0) { + control->flags |= HAS_VOLUME_0; + control->volume_channels[0] = control_channels[i][0]; + } + if (has_ch1) { + control->flags |= HAS_VOLUME_1; + control->volume_channels[1] = control_channels[i][1]; + } + } + } + if (has_psw) { + control->flags |= TYPE_PSWITCH; + if (snd_mixer_selem_has_playback_switch_joined(elem)) { + control->flags |= HAS_PSWITCH_0; + control->pswitch_channels[0] = 0; + } else { + if (has_ch0) { + control->flags |= HAS_PSWITCH_0; + control->pswitch_channels[0] = control_channels[i][0]; + } + if (has_ch1) { + control->flags |= HAS_PSWITCH_1; + control->pswitch_channels[1] = control_channels[i][1]; + } + } + } + if (merged_cswitch) { + control->flags |= TYPE_CSWITCH; + if (snd_mixer_selem_has_capture_switch_joined(elem)) { + control->flags |= HAS_CSWITCH_0; + control->cswitch_channels[0] = 0; + } else { + if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0])) { + control->flags |= HAS_CSWITCH_0; + control->cswitch_channels[0] = control_channels[i][0]; + } + if (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN && + snd_mixer_selem_has_capture_channel(elem, control_channels[i][1])) { + control->flags |= HAS_CSWITCH_1; + control->cswitch_channels[1] = control_channels[i][1]; + } + } + } + if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) { + control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1; + control->volume_channels[0] = control->volume_channels[1]; + } + if ((control->flags & (HAS_PSWITCH_0 | HAS_PSWITCH_1)) == HAS_PSWITCH_1) { + control->flags ^= HAS_PSWITCH_0 | HAS_PSWITCH_1; + control->pswitch_channels[0] = control->pswitch_channels[1]; + } + if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) { + control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1; + control->cswitch_channels[0] = control->cswitch_channels[1]; + } + if (snd_mixer_selem_is_active(control->elem)) + control->flags |= IS_ACTIVE; + create_name(control); + if (i == 0) + front_control = control; + else { + front_control->flags |= IS_MULTICH | 0; + control->flags |= IS_MULTICH | i; + } + ++control; + ++count; + } + } + } + if (view_mode != VIEW_MODE_PLAYBACK && (has_cvol || has_csw) && !merged_cswitch) { + if ((!has_cvol || snd_mixer_selem_has_capture_volume_joined(elem)) && + (!has_csw || snd_mixer_selem_has_capture_switch_joined(elem))) { + control->elem = elem; + if (has_cvol) { + control->flags |= TYPE_CVOLUME | HAS_VOLUME_0; + control->volume_channels[0] = 0; + } + if (has_csw) { + control->flags |= TYPE_CSWITCH | HAS_CSWITCH_0; + control->cswitch_channels[0] = 0; + } + if (snd_mixer_selem_is_active(control->elem)) + control->flags |= IS_ACTIVE; + create_name(control); + ++control; + ++count; + } else { + for (i = 0; i < ARRAY_SIZE(supported_channels); ++i) + has_channel[supported_channels[i]] = + snd_mixer_selem_has_capture_channel(elem, supported_channels[i]); + for (i = 0; i < ARRAY_SIZE(control_channels); ++i) { + has_ch0 = has_channel[control_channels[i][0]]; + has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN && + has_channel[control_channels[i][1]]; + if (!has_ch0 && !has_ch1) + continue; + control->elem = elem; + if (has_cvol) { + control->flags |= TYPE_CVOLUME; + if (snd_mixer_selem_has_capture_volume_joined(elem)) { + control->flags |= HAS_VOLUME_0; + control->volume_channels[0] = 0; + } else { + if (has_ch0) { + control->flags |= HAS_VOLUME_0; + control->volume_channels[0] = control_channels[i][0]; + } + if (has_ch1) { + control->flags |= HAS_VOLUME_1; + control->volume_channels[1] = control_channels[i][1]; + } + } + } + if (has_csw) { + control->flags |= TYPE_CSWITCH; + if (snd_mixer_selem_has_capture_switch_joined(elem)) { + control->flags |= HAS_CSWITCH_0; + control->cswitch_channels[0] = 0; + } else { + if (has_ch0) { + control->flags |= HAS_CSWITCH_0; + control->cswitch_channels[0] = control_channels[i][0]; + } + if (has_ch1) { + control->flags |= HAS_CSWITCH_1; + control->cswitch_channels[1] = control_channels[i][1]; + } + } + } + if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) { + control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1; + control->volume_channels[0] = control->volume_channels[1]; + } + if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) { + control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1; + control->cswitch_channels[0] = control->cswitch_channels[1]; + } + if (snd_mixer_selem_is_active(control->elem)) + control->flags |= IS_ACTIVE; + create_name(control); + if (i == 0) + front_control = control; + else { + front_control->flags |= IS_MULTICH | 0; + control->flags |= IS_MULTICH | i; + } + ++control; + ++count; + } + } + } + return count; +} + +static void search_for_focus_control(void) +{ + snd_mixer_elem_t *elem; + unsigned int i; + + elem = snd_mixer_find_selem(mixer, current_selem_id); + if (elem) + for (i = 0; i < controls_count; ++i) + if (controls[i].elem == elem) { + focus_control_index = i; + for (;;) { + ++i; + if (i >= controls_count || controls[i].elem != elem) + return; + if (controls[i].flags == current_control_flags) { + focus_control_index = i; + return; + } + } + } + focus_control_index = 0; +} + +void free_controls(void) +{ + unsigned int i; + + for (i = 0; i < controls_count; ++i) + free(controls[i].name); + free(controls); + controls = NULL; + controls_count = 0; +} + +void create_controls(void) +{ + snd_mixer_elem_t *elem; + struct control *control; + + free_controls(); + + for (elem = snd_mixer_first_elem(mixer); + elem; + elem = snd_mixer_elem_next(elem)) + controls_count += get_controls_count_for_elem(elem); + + if (controls_count > 0) { + controls = ccalloc(controls_count, sizeof *controls); + control = controls; + for (elem = snd_mixer_first_elem(mixer); + elem; + elem = snd_mixer_elem_next(elem)) + control += create_controls_for_elem(elem, control); + assert(control == controls + controls_count); + } + + compute_controls_layout(); + display_view_mode(); + + search_for_focus_control(); + refocus_control(); +} 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 <alsa/asoundlib.h> + +struct control { + snd_mixer_elem_t *elem; + char *name; + unsigned int flags; +#define TYPE_PVOLUME (1u << 4) +#define TYPE_CVOLUME (1u << 5) +#define TYPE_PSWITCH (1u << 6) +#define TYPE_CSWITCH (1u << 7) +#define TYPE_ENUM (1u << 8) +#define HAS_VOLUME_0 (1u << 9) +#define HAS_VOLUME_1 (1u << 10) +#define HAS_PSWITCH_0 (1u << 11) +#define HAS_PSWITCH_1 (1u << 12) +#define HAS_CSWITCH_0 (1u << 13) +#define HAS_CSWITCH_1 (1u << 14) +#define IS_MULTICH (1u << 15) +#define IS_ACTIVE (1u << 16) +#define MULTICH_MASK (0x0000f) + snd_mixer_selem_channel_id_t volume_channels[2]; + snd_mixer_selem_channel_id_t pswitch_channels[2]; + snd_mixer_selem_channel_id_t cswitch_channels[2]; + unsigned int enum_channel_bits; +}; + +extern struct control *controls; +extern unsigned int controls_count; + +bool are_there_any_controls(void); +void create_controls(void); +void free_controls(void); + +#endif 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 <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include CURSESINC +#include <alsa/asoundlib.h> +#include "gettext_curses.h" +#include "utils.h" +#include "mem.h" +#include "colors.h" +#include "widget.h" +#include "mixer_widget.h" +#include "mixer_controls.h" +#include "mixer_display.h" + +enum align { + ALIGN_LEFT, + ALIGN_RIGHT, + ALIGN_CENTER, +}; + +static bool screen_too_small; +static bool has_info_items; + +static int info_items_left; +static int info_items_width; + +static int visible_controls; +static int first_visible_control_index; +static int first_control_x; +static int control_width; +static int control_name_width; + +static int base_y; +static int volume_height; +static int cswitch_y; +static int values_y; +static int name_y; +static int channel_name_y; + +static void display_string_in_field(int y, int x, const char *s, int width, enum align align) +{ + int string_width; + const char *s_end; + int spaces; + int cur_y, cur_x; + + wmove(mixer_widget.window, y, x); + string_width = width; + s_end = mbs_at_width(s, &string_width, -1); + if (string_width >= width) { + waddnstr(mixer_widget.window, s, s_end - s); + } else { + if (align != ALIGN_LEFT) { + spaces = width - string_width; + if (align == ALIGN_CENTER) + spaces /= 2; + if (spaces > 0) + wprintw(mixer_widget.window, "%*s", spaces, ""); + } + waddstr(mixer_widget.window, s); + if (align != ALIGN_RIGHT) { + getyx(mixer_widget.window, cur_y, cur_x); + if (cur_y == y) { + spaces = x + width - cur_x; + if (spaces > 0) + wprintw(mixer_widget.window, "%*s", spaces, ""); + } + } + } +} + +void init_mixer_layout(void) +{ + const char *labels_left[4] = { + _("Card:"), + _("Chip:"), + _("View:"), + _("Item:"), + }; + const char *labels_right[4] = { + _("F1: Help"), + _("F2: System information"), + _("F6: Select sound card"), + _("Esc: Exit"), + }; + unsigned int label_width_left, label_width_right; + unsigned int right_x, i; + + screen_too_small = screen_lines < 14 || screen_cols < 12; + has_info_items = screen_lines >= 6; + if (!has_info_items) + return; + + label_width_left = get_max_mbs_width(labels_left, 4); + label_width_right = get_max_mbs_width(labels_right, 4); + if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols) + label_width_right = 0; + if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols) + label_width_left = 0; + + info_items_left = label_width_left ? 3 + label_width_left : 2; + right_x = screen_cols - label_width_right - 2; + info_items_width = right_x - info_items_left; + if (info_items_width < 1) { + has_info_items = FALSE; + return; + } + + wattrset(mixer_widget.window, attr_mixer_text); + if (label_width_left) + for (i = 0; i < 4; ++i) + display_string_in_field(1 + i, 2, labels_left[i], + label_width_left, ALIGN_RIGHT); + if (label_width_right) + for (i = 0; i < 4; ++i) + display_string_in_field(1 + i, right_x, labels_right[i], + label_width_right, ALIGN_LEFT); +} + +void display_card_info(void) +{ + snd_hctl_t *hctl; + snd_ctl_t *ctl; + snd_ctl_card_info_t *card_info; + const char *card_name = NULL; + const char *mixer_name = NULL; + int err; + + if (!has_info_items) + return; + + snd_ctl_card_info_alloca(&card_info); + if (mixer_device_name) + err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl); + else + err = -1; + if (err >= 0) { + ctl = snd_hctl_ctl(hctl); + err = snd_ctl_card_info(ctl, card_info); + if (err >= 0) { + card_name = snd_ctl_card_info_get_name(card_info); + mixer_name = snd_ctl_card_info_get_mixername(card_info); + } + } + + if (card_name) + wattrset(mixer_widget.window, attr_mixer_active); + else { + wattrset(mixer_widget.window, attr_mixer_text); + if (unplugged) + card_name = _("(unplugged)"); + else + card_name = "-"; + } + display_string_in_field(1, info_items_left, card_name, info_items_width, ALIGN_LEFT); + + if (mixer_name) + wattrset(mixer_widget.window, attr_mixer_active); + else { + wattrset(mixer_widget.window, attr_mixer_text); + mixer_name = "-"; + } + display_string_in_field(2, info_items_left, mixer_name, info_items_width, ALIGN_LEFT); +} + +void display_view_mode(void) +{ + const char *modes[3] = { + _("Playback"), + _("Capture"), + _("All"), + }; + unsigned int widths[3]; + bool has_view_mode; + int i; + + if (!has_info_items) + return; + + has_view_mode = controls_count > 0 || are_there_any_controls(); + for (i = 0; i < 3; ++i) + widths[i] = get_mbs_width(modes[i]); + if (4 + widths[0] + 6 + widths[1] + 6 + widths[2] + 1 <= info_items_width) { + wmove(mixer_widget.window, 3, info_items_left); + wattrset(mixer_widget.window, attr_mixer_text); + for (i = 0; i < 3; ++i) { + wprintw(mixer_widget.window, "F%c:", '3' + i); + if (has_view_mode && (int)view_mode == i) { + wattrset(mixer_widget.window, attr_mixer_active); + wprintw(mixer_widget.window, "[%s]", modes[i]); + wattrset(mixer_widget.window, attr_mixer_text); + } else { + wprintw(mixer_widget.window, " %s ", modes[i]); + } + if (i < 2) + waddch(mixer_widget.window, ' '); + } + } else { + wattrset(mixer_widget.window, attr_mixer_active); + display_string_in_field(3, info_items_left, + has_view_mode ? modes[view_mode] : "", + info_items_width, ALIGN_LEFT); + } +} + +static char *format_gain(long db) +{ + if (db != SND_CTL_TLV_DB_GAIN_MUTE) + return casprintf("%.2f", db / 100.0); + else + return cstrdup(_("mute")); +} + +static void display_focus_item_info(void) +{ + struct control *control; + unsigned int index; + char buf[64]; + long db, db2; + int sw, sw2; + char *dbs, *dbs2; + char *value_info; + char *item_info; + int err; + + if (!has_info_items) + return; + wattrset(mixer_widget.window, attr_mixer_active); + if (!controls_count || screen_too_small) { + display_string_in_field(4, info_items_left, "", info_items_width, ALIGN_LEFT); + return; + } + control = &controls[focus_control_index]; + value_info = NULL; + if (control->flags & TYPE_ENUM) { + err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index); + if (err >= 0) + err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf); + if (err >= 0) + value_info = casprintf(" [%s]", buf); + } else if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) { + int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *); + + if (control->flags & TYPE_PVOLUME) + get_vol_func = snd_mixer_selem_get_playback_dB; + else + get_vol_func = snd_mixer_selem_get_capture_dB; + if (!(control->flags & HAS_VOLUME_1)) { + err = get_vol_func(control->elem, control->volume_channels[0], &db); + if (err >= 0) { + dbs = format_gain(db); + value_info = casprintf(" [%s %s]", _("dB gain:"), dbs); + free(dbs); + } + } else { + err = get_vol_func(control->elem, control->volume_channels[0], &db); + if (err >= 0) + err = get_vol_func(control->elem, control->volume_channels[1], &db2); + if (err >= 0) { + dbs = format_gain(db); + dbs2 = format_gain(db2); + value_info = casprintf(_(" [%s %s, %s]"), _("dB gain:"), dbs, dbs2); + free(dbs); + free(dbs2); + } + } + } else if (control->flags & TYPE_PSWITCH) { + if (!(control->flags & HAS_PSWITCH_1)) { + err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw); + if (err >= 0 && !sw) + value_info = casprintf(" [%s]", _("Off")); + } else { + err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw); + if (err >= 0) + err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &sw2); + if (err >= 0 && (!sw || !sw2)) + value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off")); + } + } else if (control->flags & TYPE_CSWITCH) { + if (!(control->flags & HAS_CSWITCH_1)) { + err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw); + if (err >= 0 && !sw) + value_info = casprintf(" [%s]", _("Off")); + } else { + err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw); + if (err >= 0) + err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &sw2); + if (err >= 0 && (!sw || !sw2)) + value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off")); + } + } + item_info = casprintf("%s%s", control->name, value_info ? value_info : ""); + free(value_info); + display_string_in_field(4, info_items_left, item_info, info_items_width, ALIGN_LEFT); + free(item_info); +} + +static void clear_controls_display(void) +{ + int i; + + wattrset(mixer_widget.window, attr_mixer_frame); + for (i = 5; i < screen_lines - 1; ++i) + mvwprintw(mixer_widget.window, i, 1, "%*s", screen_cols - 2, ""); +} + +static void center_string(int line, const char *s) +{ + int width = get_mbs_width(s); + if (width <= screen_cols - 2) + mvwaddstr(mixer_widget.window, line, (screen_cols - width) / 2, s); +} + +static void display_unplugged(void) +{ + int lines, top, left; + bool boojum; + + lines = screen_lines - 6; + if (lines < 2) + return; + top = lines / 2; + boojum = lines >= 10 && screen_cols >= 48; + top -= boojum ? 5 : 1; + if (top < 5) + top = 5; + if (boojum) { + left = (screen_cols - 46) / 2; + wattrset(mixer_widget.window, attr_mixer_text); + mvwaddstr(mixer_widget.window, top + 0, left, "In the midst of the word he was trying to say,"); + mvwaddstr(mixer_widget.window, top + 1, left + 2, "In the midst of his laughter and glee,"); + mvwaddstr(mixer_widget.window, top + 2, left, "He had softly and suddenly vanished away---"); + mvwaddstr(mixer_widget.window, top + 3, left + 2, "For the Snark was a Boojum, you see."); + mvwchgat(mixer_widget.window, top + 3, left + 16, 3, /* ^^^ */ + attr_mixer_text | A_BOLD, PAIR_NUMBER(attr_mixer_text), NULL); + mvwaddstr(mixer_widget.window, top + 5, left, "(Lewis Carroll, \"The Hunting of the Snark\")"); + top += 8; + } + wattrset(mixer_widget.window, attr_errormsg); + center_string(top, _("The sound device was unplugged.")); + center_string(top + 1, _("Press F6 to select another sound card.")); +} + +static void display_no_controls(void) +{ + int y; + const char *msg; + + y = (screen_lines - 6) / 2 - 1; + if (y < 5) + y = 5; + if (y >= screen_lines - 1) + return; + wattrset(mixer_widget.window, attr_infomsg); + if (view_mode == VIEW_MODE_PLAYBACK && are_there_any_controls()) + msg = _("This sound device does not have any playback controls."); + else if (view_mode == VIEW_MODE_CAPTURE && are_there_any_controls()) + msg = _("This sound device does not have any capture controls."); + else + msg = _("This sound device does not have any controls."); + center_string(y, msg); +} + +static void display_string_centered_in_control(int y, int col, const char *s, int width) +{ + int left, x; + + left = first_control_x + col * (control_width + 1); + x = left + (control_width - width) / 2; + display_string_in_field(y, x, s, width, ALIGN_CENTER); +} + +static void display_control(unsigned int control_index) +{ + struct control *control; + int col; + int i; + int left, frame_left; + int bar_height, value; + long volumes[2]; + long min, max; + int switches[2]; + unsigned int index; + const char *s; + char buf[64]; + int err; + + control = &controls[control_index]; + col = control_index - first_visible_control_index; + left = first_control_x + col * (control_width + 1); + frame_left = left + (control_width - 4) / 2; + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, attr_ctl_frame); + else + wattrset(mixer_widget.window, attr_ctl_inactive); + if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) { + mvwaddch(mixer_widget.window, base_y - volume_height - 1, frame_left, ACS_ULCORNER); + waddch(mixer_widget.window, ACS_HLINE); + waddch(mixer_widget.window, ACS_HLINE); + waddch(mixer_widget.window, ACS_URCORNER); + for (i = 0; i < volume_height; ++i) { + mvwaddch(mixer_widget.window, base_y - i - 1, frame_left, ACS_VLINE); + mvwaddch(mixer_widget.window, base_y - i - 1, frame_left + 3, ACS_VLINE); + } + mvwaddch(mixer_widget.window, base_y, frame_left, + control->flags & TYPE_PSWITCH ? ACS_LTEE : ACS_LLCORNER); + waddch(mixer_widget.window, ACS_HLINE); + waddch(mixer_widget.window, ACS_HLINE); + waddch(mixer_widget.window, + control->flags & TYPE_PSWITCH ? ACS_RTEE : ACS_LRCORNER); + } else if (control->flags & TYPE_PSWITCH) { + mvwaddch(mixer_widget.window, base_y, frame_left, ACS_ULCORNER); + waddch(mixer_widget.window, ACS_HLINE); + waddch(mixer_widget.window, ACS_HLINE); + waddch(mixer_widget.window, ACS_URCORNER); + } + if (control->flags & TYPE_PSWITCH) { + mvwaddch(mixer_widget.window, base_y + 1, frame_left, ACS_VLINE); + mvwaddch(mixer_widget.window, base_y + 1, frame_left + 3, ACS_VLINE); + mvwaddch(mixer_widget.window, base_y + 2, frame_left, ACS_LLCORNER); + waddch(mixer_widget.window, ACS_HLINE); + waddch(mixer_widget.window, ACS_HLINE); + waddch(mixer_widget.window, ACS_LRCORNER); + } + if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) { + int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *); + + if (control->flags & TYPE_PVOLUME) + get_vol_func = snd_mixer_selem_get_playback_volume; + else + get_vol_func = snd_mixer_selem_get_capture_volume; + err = get_vol_func(control->elem, control->volume_channels[0], &volumes[0]); + if (err >= 0 && (control->flags & HAS_VOLUME_1)) + err = get_vol_func(control->elem, control->volume_channels[1], &volumes[1]); + else + volumes[1] = volumes[0]; + if (err < 0) + return; + if (control->flags & TYPE_PVOLUME) + err = snd_mixer_selem_get_playback_volume_range(control->elem, &min, &max); + else + err = snd_mixer_selem_get_capture_volume_range(control->elem, &min, &max); + if (err < 0) + return; + + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, 0); + bar_height = ((volumes[0] - min) * volume_height + max - min - 1) / (max - min); + for (i = 0; i < volume_height; ++i) + mvwaddch(mixer_widget.window, base_y - i - 1, frame_left + 1, + i + 1 <= bar_height + ? ACS_CKBOARD | (control->flags & IS_ACTIVE ? attr_ctl_bar : 0) + : ' ' | (control->flags & IS_ACTIVE ? attr_ctl_frame : 0)); + bar_height = ((volumes[1] - min) * volume_height + max - min - 1) / (max - min); + for (i = 0; i < volume_height; ++i) + mvwaddch(mixer_widget.window, base_y - i - 1, frame_left + 2, + i + 1 <= bar_height + ? ACS_CKBOARD | (control->flags & IS_ACTIVE ? attr_ctl_bar : 0) + : ' ' | (control->flags & IS_ACTIVE ? attr_ctl_frame : 0)); + + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, attr_mixer_active); + value = ((volumes[0] - min) * 100 + (max - min) / 2) / (max - min); + if (!(control->flags & HAS_VOLUME_1)) { + sprintf(buf, "%d", value); + display_string_in_field(values_y, frame_left - 2, buf, 8, ALIGN_CENTER); + } else { + mvwprintw(mixer_widget.window, values_y, frame_left - 2, "%3d", value); + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, attr_ctl_frame); + waddstr(mixer_widget.window, "<>"); + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, attr_mixer_active); + value = ((volumes[1] - min) * 100 + (max - min) / 2) / (max - min); + wprintw(mixer_widget.window, "%-3d", value); + } + } + + if (control->flags & TYPE_PSWITCH) { + err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &switches[0]); + if (err >= 0 && (control->flags & HAS_PSWITCH_1)) + err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &switches[1]); + else + switches[1] = switches[0]; + if (err < 0) + return; + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, 0); + mvwaddch(mixer_widget.window, base_y + 1, frame_left + 1, + switches[0] + /* TRANSLATORS: playback on; one character */ + ? _("O")[0] | (control->flags & IS_ACTIVE ? attr_ctl_nomute : 0) + /* TRANSLATORS: playback muted; one character */ + : _("M")[0] | (control->flags & IS_ACTIVE ? attr_ctl_mute : 0)); + waddch(mixer_widget.window, + switches[1] + ? _("O")[0] | (control->flags & IS_ACTIVE ? attr_ctl_nomute : 0) + : _("M")[0] | (control->flags & IS_ACTIVE ? attr_ctl_mute : 0)); + } + + if (control->flags & TYPE_CSWITCH) { + err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &switches[0]); + if (err >= 0 && (control->flags & HAS_CSWITCH_1)) + err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &switches[1]); + else + switches[1] = switches[0]; + if (err < 0) + return; + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, switches[0] ? attr_ctl_capture : attr_ctl_nocapture); + /* TRANSLATORS: "left"; no more than two characters */ + display_string_in_field(cswitch_y - 1, frame_left - 2, switches[0] ? _("L") : "", 2, ALIGN_RIGHT); + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, switches[1] ? attr_ctl_capture : attr_ctl_nocapture); + /* TRANSLATORS: "right"; no more than two characters */ + display_string_in_field(cswitch_y - 1, frame_left + 4, switches[1] ? _("R") : "", 2, ALIGN_LEFT); + /* TRANSLATORS: no more than eight characters */ + s = _("CAPTURE"); + if (switches[0] || switches[1]) { + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, attr_ctl_capture); + display_string_in_field(cswitch_y, frame_left - 2, s, 8, ALIGN_CENTER); + } else { + i = get_mbs_width(s); + if (i > 8) + i = 8; + memset(buf, '-', i); + buf[i] = '\0'; + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, attr_ctl_nocapture); + display_string_in_field(cswitch_y, frame_left - 2, buf, 8, ALIGN_CENTER); + } + } + + if (control->flags & TYPE_ENUM) { + err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index); + if (err < 0) + return; + err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf); + if (err < 0) + return; + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, attr_mixer_active); + display_string_centered_in_control(base_y, col, buf, control_width); + } + + if (control_index == focus_control_index) { + i = first_control_x + col * (control_width + 1) + (control_width - control_name_width) / 2; + wattrset(mixer_widget.window, attr_ctl_mark_focus); + mvwaddch(mixer_widget.window, name_y, i - 1, '<'); + mvwaddch(mixer_widget.window, name_y, i + control_name_width, '>'); + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, attr_ctl_label_focus); + else + wattrset(mixer_widget.window, attr_ctl_label_inactive); + } else { + if (control->flags & IS_ACTIVE) + wattrset(mixer_widget.window, attr_ctl_label); + else + wattrset(mixer_widget.window, attr_ctl_label_inactive); + } + display_string_centered_in_control(name_y, col, control->name, control_name_width); + if (channel_name_y > name_y) { + if (control->flags & IS_MULTICH) { + switch (control->flags & MULTICH_MASK) { + case 0: + default: + s = _("Front"); + break; + case 1: + s = _("Rear"); + break; + case 2: + s = _("Center"); + break; + case 3: + s = _("Woofer"); + break; + case 4: + s = _("Side"); + break; + } + } else { + s = ""; + wattrset(mixer_widget.window, attr_mixer_frame); + } + display_string_centered_in_control(channel_name_y, col, s, + control_name_width); + } +} + +static void display_scroll_indicators(void) +{ + int y0, y1, y; + chtype left, right; + + if (screen_too_small) + return; + y0 = screen_lines * 3 / 8; + y1 = screen_lines * 5 / 8; + left = first_visible_control_index > 0 ? ACS_LARROW : ACS_VLINE; + right = first_visible_control_index + visible_controls < controls_count + ? ACS_RARROW : ACS_VLINE; + wattrset(mixer_widget.window, attr_mixer_frame); + for (y = y0; y <= y1; ++y) { + mvwaddch(mixer_widget.window, y, 0, left); + mvwaddch(mixer_widget.window, y, screen_cols - 1, right); + } +} + +void display_controls(void) +{ + unsigned int i; + + if (first_visible_control_index > controls_count - visible_controls) + first_visible_control_index = controls_count - visible_controls; + if (first_visible_control_index > focus_control_index) + first_visible_control_index = focus_control_index; + else if (first_visible_control_index < focus_control_index - visible_controls + 1 && visible_controls) + first_visible_control_index = focus_control_index - visible_controls + 1; + + clear_controls_display(); + + display_focus_item_info(); + + if (controls_count > 0) { + if (!screen_too_small) + for (i = 0; i < visible_controls; ++i) + display_control(first_visible_control_index + i); + } else if (unplugged) { + display_unplugged(); + } else if (mixer_device_name) { + display_no_controls(); + } + display_scroll_indicators(); + controls_changed = FALSE; +} + +void compute_controls_layout(void) +{ + bool any_volume, any_pswitch, any_cswitch, any_multich; + int max_width, name_len; + int height, space; + unsigned int i; + + if (controls_count == 0 || screen_too_small) { + visible_controls = 0; + return; + } + + any_volume = FALSE; + any_pswitch = FALSE; + any_cswitch = FALSE; + any_multich = FALSE; + for (i = 0; i < controls_count; ++i) { + if (controls[i].flags & (TYPE_PVOLUME | TYPE_CVOLUME)) + any_volume = 1; + if (controls[i].flags & TYPE_PSWITCH) + any_pswitch = 1; + if (controls[i].flags & TYPE_CSWITCH) + any_cswitch = 1; + if (controls[i].flags & IS_MULTICH) + any_multich = 1; + } + + max_width = 8; + for (i = 0; i < controls_count; ++i) { + name_len = strlen(controls[i].name); + if (name_len > max_width) + max_width = name_len; + } + max_width = (max_width + 1) & ~1; + + control_width = (screen_cols - 3 - (int)controls_count) / controls_count; + if (control_width < 8) + control_width = 8; + if (control_width > max_width) + control_width = max_width; + if (control_width > screen_cols - 4) + control_width = screen_cols - 4; + + visible_controls = (screen_cols - 3) / (control_width + 1); + if (visible_controls > controls_count) + visible_controls = controls_count; + + first_control_x = 2 + (screen_cols - 3 - visible_controls * (control_width + 1)) / 2; + + if (control_width < max_width) + control_name_width = control_width; + else + control_name_width = max_width; + + height = 2; + if (any_volume) + height += 2; + if (any_pswitch) + height += 2; + if (any_cswitch) + height += 1; + if (any_multich) + height += 1; + if (any_volume) { + space = screen_lines - 6 - height; + if (space <= 1) + volume_height = 1; + else if (space <= 10) + volume_height = space; + else + volume_height = 10 + (space - 10) / 2; + height += volume_height; + } + + space = screen_lines - 6 - height; + channel_name_y = screen_lines - 2 - space / 2; + name_y = channel_name_y - any_multich; + values_y = name_y - any_volume; + cswitch_y = values_y - any_cswitch; + base_y = cswitch_y - 1 - 2 * any_pswitch; +} 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 <timj@gtk.org> + * Jaroslav Kysela <perex@perex.cz> + * Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <alsa/asoundlib.h> +#include "gettext_curses.h" +#include "version.h" +#include "utils.h" +#include "die.h" +#include "mem.h" +#include "colors.h" +#include "widget.h" +#include "textbox.h" +#include "proc_files.h" +#include "card_select.h" +#include "mixer_controls.h" +#include "mixer_display.h" +#include "mixer_widget.h" + +snd_mixer_t *mixer; +char *mixer_device_name; +bool unplugged; + +struct widget mixer_widget; + +enum view_mode view_mode; + +int focus_control_index; +snd_mixer_selem_id_t *current_selem_id; +unsigned int current_control_flags; + +bool controls_changed; + +enum channel_mask { + LEFT = 1, + RIGHT = 2, +}; + +static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask) +{ + if (mask & (SND_CTL_EVENT_MASK_REMOVE | + SND_CTL_EVENT_MASK_INFO | + SND_CTL_EVENT_MASK_VALUE)) + controls_changed = TRUE; + return 0; +} + +static int mixer_callback(snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem) +{ + if (mask & SND_CTL_EVENT_MASK_ADD) { + snd_mixer_elem_set_callback(elem, elem_callback); + controls_changed = TRUE; + } + return 0; +} + +void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt) +{ + int err; + + err = snd_mixer_open(&mixer, 0); + if (err < 0) + fatal_alsa_error(_("cannot open mixer"), err); + + mixer_device_name = cstrdup(selem_regopt->device); + err = snd_mixer_selem_register(mixer, selem_regopt, NULL); + if (err < 0) + fatal_alsa_error(_("cannot open mixer"), err); + + snd_mixer_set_callback(mixer, mixer_callback); + + err = snd_mixer_load(mixer); + if (err < 0) + fatal_alsa_error(_("cannot load mixer controls"), err); + + err = snd_mixer_selem_id_malloc(¤t_selem_id); + if (err < 0) + fatal_error("out of memory"); +} + +static void set_view_mode(enum view_mode m) +{ + view_mode = m; + create_controls(); +} + +static void close_hctl(void) +{ + free_controls(); + if (mixer_device_name) { + snd_mixer_detach(mixer, mixer_device_name); + free(mixer_device_name); + mixer_device_name = NULL; + } +} + +static void check_unplugged(void) +{ + snd_hctl_t *hctl; + snd_ctl_t *ctl; + unsigned int state; + int err; + + unplugged = FALSE; + if (mixer_device_name) { + err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl); + if (err >= 0) { + ctl = snd_hctl_ctl(hctl); + /* just any random function that does an ioctl() */ + err = snd_ctl_get_power_state(ctl, &state); + if (err == -ENODEV) + unplugged = TRUE; + } + } +} + +void close_mixer_device(void) +{ + check_unplugged(); + close_hctl(); + + display_card_info(); + set_view_mode(view_mode); +} + +bool select_card_by_name(const char *device_name) +{ + int err; + bool opened; + char *msg; + + close_hctl(); + unplugged = FALSE; + + opened = FALSE; + if (device_name) { + err = snd_mixer_attach(mixer, device_name); + if (err >= 0) + opened = TRUE; + else { + msg = casprintf(_("Cannot open mixer device '%s'."), device_name); + show_alsa_error(msg, err); + free(msg); + } + } + if (opened) { + mixer_device_name = cstrdup(device_name); + + err = snd_mixer_load(mixer); + if (err < 0) + fatal_alsa_error(_("cannot load mixer controls"), err); + } + + display_card_info(); + set_view_mode(view_mode); + return opened; +} + +static void show_help(void) +{ + const char *help[] = { + _("Esc Exit"), + _("F1 ? H Help"), + _("F2 / System information"), + _("F3 Show playback controls"), + _("F4 Show capture controls"), + _("F5 Show all controls"), + _("Tab Toggle view mode (F3/F4/F5)"), + _("F6 S Select sound card"), + _("L Redraw screen"), + "", + _("Left Move to the previous control"), + _("Right Move to the next control"), + "", + _("Up/Down Change volume"), + _("+ - Change volume"), + _("Page Up/Dn Change volume in big steps"), + _("End Set volume to 0%"), + _("0-9 Set volume to 0%-90%"), + _("Q W E Increase left/both/right volumes"), + /* TRANSLATORS: or Y instead of Z */ + _("Z X C Decrease left/both/right volumes"), + _("B Balance left and right volumes"), + "", + _("M Toggle mute"), + /* TRANSLATORS: or , . */ + _("< > Toggle left/right mute"), + "", + _("Space Toggle capture"), + /* TRANSLATORS: or Insert Delete */ + _("; ' Toggle left/right capture"), + "", + _("Authors:"), + _(" Tim Janik <timj@gtk.org>"), + _(" Jaroslav Kysela <perex@perex.cz>"), + _(" Clemens Ladisch <clemens@ladisch.de>"), + }; + show_text(help, ARRAY_SIZE(help), _("Help")); +} + +void refocus_control(void) +{ + if (focus_control_index < controls_count) { + snd_mixer_selem_get_id(controls[focus_control_index].elem, current_selem_id); + current_control_flags = controls[focus_control_index].flags; + } + + display_controls(); +} + +static struct control *get_focus_control(unsigned int type) +{ + if (focus_control_index >= 0 && + focus_control_index < controls_count && + (controls[focus_control_index].flags & IS_ACTIVE) && + (controls[focus_control_index].flags & type)) + return &controls[focus_control_index]; + else + return NULL; +} + +static void change_enum_to_percent(struct control *control, int value) +{ + unsigned int i; + unsigned int index; + unsigned int new_index; + int items; + int err; + + i = ffs(control->enum_channel_bits) - 1; + err = snd_mixer_selem_get_enum_item(control->elem, i, &index); + if (err < 0) + return; + new_index = index; + if (value == 0) { + new_index = 0; + } else if (value == 100) { + items = snd_mixer_selem_get_enum_items(control->elem); + if (items < 1) + return; + new_index = items - 1; + } + if (new_index == index) + return; + for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i) + if (control->enum_channel_bits & (1 << i)) + snd_mixer_selem_set_enum_item(control->elem, i, new_index); +} + +static void change_enum_relative(struct control *control, int delta) +{ + int items; + unsigned int i; + unsigned int index; + int new_index; + int err; + + items = snd_mixer_selem_get_enum_items(control->elem); + if (items < 1) + return; + err = snd_mixer_selem_get_enum_item(control->elem, 0, &index); + if (err < 0) + return; + new_index = (int)index + delta; + if (new_index < 0) + new_index = 0; + else if (new_index >= items) + new_index = items - 1; + if (new_index == index) + return; + for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i) + if (control->enum_channel_bits & (1 << i)) + snd_mixer_selem_set_enum_item(control->elem, i, new_index); +} + +static void change_volume_to_percent(struct control *control, int value, unsigned int channels) +{ + int (*get_range_func)(snd_mixer_elem_t *, long *, long *); + int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long); + long min, max; + int err; + + if (!(control->flags & HAS_VOLUME_1)) + channels = LEFT; + if (control->flags & TYPE_PVOLUME) { + get_range_func = snd_mixer_selem_get_playback_volume_range; + set_func = snd_mixer_selem_set_playback_volume; + } else { + get_range_func = snd_mixer_selem_get_capture_volume_range; + set_func = snd_mixer_selem_set_capture_volume; + } + err = get_range_func(control->elem, &min, &max); + if (err < 0) + return; + if (channels & LEFT) + set_func(control->elem, control->volume_channels[0], min + (max - min) * value / 100); + if (channels & RIGHT) + set_func(control->elem, control->volume_channels[1], min + (max - min) * value / 100); +} + +static void change_volume_relative(struct control *control, int delta, unsigned int channels) +{ + int (*get_range_func)(snd_mixer_elem_t *, long *, long *); + int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *); + int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long); + long min, max; + long left, right; + long value; + int err; + + if (!(control->flags & HAS_VOLUME_1)) + channels = LEFT; + if (control->flags & TYPE_PVOLUME) { + get_range_func = snd_mixer_selem_get_playback_volume_range; + get_func = snd_mixer_selem_get_playback_volume; + set_func = snd_mixer_selem_set_playback_volume; + } else { + get_range_func = snd_mixer_selem_get_capture_volume_range; + get_func = snd_mixer_selem_get_capture_volume; + set_func = snd_mixer_selem_set_capture_volume; + } + err = get_range_func(control->elem, &min, &max); + if (err < 0) + return; + if (channels & LEFT) { + err = get_func(control->elem, control->volume_channels[0], &left); + if (err < 0) + return; + } + if (channels & RIGHT) { + err = get_func(control->elem, control->volume_channels[1], &right); + if (err < 0) + return; + } + if (channels & LEFT) { + value = left + delta; + if (value < min) + value = min; + else if (value > max) + value = max; + if (value != left) + set_func(control->elem, control->volume_channels[0], value); + } + if (channels & RIGHT) { + value = right + delta; + if (value < min) + value = min; + else if (value > max) + value = max; + if (value != right) + set_func(control->elem, control->volume_channels[1], value); + } +} + +static void change_control_to_percent(int value, unsigned int channels) +{ + struct control *control; + + control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM); + if (!control) + return; + if (control->flags & TYPE_ENUM) + change_enum_to_percent(control, value); + else + change_volume_to_percent(control, value, channels); + display_controls(); +} + +static void change_control_relative(int delta, unsigned int channels) +{ + struct control *control; + + control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM); + if (!control) + return; + if (control->flags & TYPE_ENUM) + change_enum_relative(control, delta); + else + change_volume_relative(control, delta, channels); + display_controls(); +} + +static void toggle_switches(unsigned int type, unsigned int channels) +{ + struct control *control; + unsigned int switch_1_mask; + int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *); + int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int); + snd_mixer_selem_channel_id_t channel_ids[2]; + int left, right; + int err; + + control = get_focus_control(type); + if (!control) + return; + if (type == TYPE_PSWITCH) { + switch_1_mask = HAS_PSWITCH_1; + get_func = snd_mixer_selem_get_playback_switch; + set_func = snd_mixer_selem_set_playback_switch; + channel_ids[0] = control->pswitch_channels[0]; + channel_ids[1] = control->pswitch_channels[1]; + } else { + switch_1_mask = HAS_CSWITCH_1; + get_func = snd_mixer_selem_get_capture_switch; + set_func = snd_mixer_selem_set_capture_switch; + channel_ids[0] = control->cswitch_channels[0]; + channel_ids[1] = control->cswitch_channels[1]; + } + if (!(control->flags & switch_1_mask)) + channels = LEFT; + if (channels & LEFT) { + err = get_func(control->elem, channel_ids[0], &left); + if (err < 0) + return; + } + if (channels & RIGHT) { + err = get_func(control->elem, channel_ids[1], &right); + if (err < 0) + return; + } + if (channels & LEFT) + set_func(control->elem, channel_ids[0], !left); + if (channels & RIGHT) + set_func(control->elem, channel_ids[1], !right); + display_controls(); +} + +static void toggle_mute(unsigned int channels) +{ + toggle_switches(TYPE_PSWITCH, channels); +} + +static void toggle_capture(unsigned int channels) +{ + toggle_switches(TYPE_CSWITCH, channels); +} + +static void balance_volumes(void) +{ + struct control *control; + long left, right; + int err; + + control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME); + if (!control || !(control->flags & HAS_VOLUME_1)) + return; + if (control->flags & TYPE_PVOLUME) { + err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[0], &left); + if (err < 0) + return; + err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[1], &right); + if (err < 0) + return; + } else { + err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[0], &left); + if (err < 0) + return; + err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[1], &right); + if (err < 0) + return; + } + left = (left + right) / 2; + if (control->flags & TYPE_PVOLUME) { + snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[0], left); + snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[1], left); + } else { + snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[0], left); + snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[1], left); + } + display_controls(); +} + +static void on_handle_key(int key) +{ + switch (key) { + case 27: + case KEY_CANCEL: + case KEY_F(10): + mixer_widget.close(); + break; + case KEY_F(1): + case KEY_HELP: + case 'H': + case 'h': + case '?': + show_help(); + break; + case KEY_F(2): + case '/': + create_proc_files_list(); + break; + case KEY_F(3): + set_view_mode(VIEW_MODE_PLAYBACK); + break; + case KEY_F(4): + set_view_mode(VIEW_MODE_CAPTURE); + break; + case KEY_F(5): + set_view_mode(VIEW_MODE_ALL); + break; + case '\t': + set_view_mode((enum view_mode)((view_mode + 1) % VIEW_MODE_COUNT)); + break; + case KEY_F(6): + case 'S': + case 's': + create_card_select_list(); + break; + case KEY_REFRESH: + case 12: + case 'L': + case 'l': + clearok(mixer_widget.window, TRUE); + display_controls(); + break; + case KEY_LEFT: + case 'P': + case 'p': + if (focus_control_index > 0) { + --focus_control_index; + refocus_control(); + } + break; + case KEY_RIGHT: + case 'N': + case 'n': + if (focus_control_index < controls_count - 1) { + ++focus_control_index; + refocus_control(); + } + break; + case KEY_PPAGE: + change_control_relative(5, LEFT | RIGHT); + break; + case KEY_NPAGE: + change_control_relative(-5, LEFT | RIGHT); + break; +#if 0 + case KEY_BEG: + case KEY_HOME: + change_control_to_percent(100, LEFT | RIGHT); + break; +#endif + case KEY_LL: + case KEY_END: + change_control_to_percent(0, LEFT | RIGHT); + break; + case KEY_UP: + case '+': + case 'K': + case 'k': + case 'W': + case 'w': + change_control_relative(1, LEFT | RIGHT); + break; + case KEY_DOWN: + case '-': + case 'J': + case 'j': + case 'X': + case 'x': + change_control_relative(-1, LEFT | RIGHT); + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + change_control_to_percent((key - '0') * 10, LEFT | RIGHT); + break; + case 'Q': + case 'q': + change_control_relative(1, LEFT); + break; + case 'Y': + case 'y': + case 'Z': + case 'z': + change_control_relative(-1, LEFT); + break; + case 'E': + case 'e': + change_control_relative(1, RIGHT); + break; + case 'C': + case 'c': + change_control_relative(-1, RIGHT); + break; + case 'M': + case 'm': + toggle_mute(LEFT | RIGHT); + break; + case 'B': + case 'b': + case '=': + balance_volumes(); + break; + case '<': + case ',': + toggle_mute(LEFT); + break; + case '>': + case '.': + toggle_mute(RIGHT); + break; + case ' ': + toggle_capture(LEFT | RIGHT); + break; + case KEY_IC: + case ';': + toggle_capture(LEFT); + break; + case KEY_DC: + case '\'': + toggle_capture(RIGHT); + break; + } +} + +static void create(void) +{ + static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " "; + + widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0, + attr_mixer_frame, WIDGET_BORDER); + if (screen_cols >= (sizeof(title) - 1) + 2) { + wattrset(mixer_widget.window, attr_mixer_active); + mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title); + } + init_mixer_layout(); + display_card_info(); + set_view_mode(view_mode); +} + +static void on_window_size_changed(void) +{ + create(); +} + +static void on_close(void) +{ + widget_free(&mixer_widget); +} + +void mixer_shutdown(void) +{ + free_controls(); + if (mixer) + snd_mixer_close(mixer); + if (current_selem_id) + snd_mixer_selem_id_free(current_selem_id); +} + +struct widget mixer_widget = { + .handle_key = on_handle_key, + .window_size_changed = on_window_size_changed, + .close = on_close, +}; + +void create_mixer_widget(void) +{ + create(); +} 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 <alsa/asoundlib.h> +#include "widget.h" + +enum view_mode { + VIEW_MODE_PLAYBACK, + VIEW_MODE_CAPTURE, + VIEW_MODE_ALL, + VIEW_MODE_COUNT, +}; + +extern snd_mixer_t *mixer; +extern char *mixer_device_name; +extern bool unplugged; + +extern struct widget mixer_widget; + +extern enum view_mode view_mode; + +extern int focus_control_index; +extern snd_mixer_selem_id_t *current_selem_id; +extern unsigned int current_control_flags; + +extern bool controls_changed; + +void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt); +void create_mixer_widget(void); +void mixer_shutdown(void); +void close_mixer_device(void); +bool select_card_by_name(const char *device_name); +void refocus_control(void); + +#endif 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 <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include <assert.h> +#include <menu.h> +#include <unistd.h> +#include "gettext_curses.h" +#include "utils.h" +#include "die.h" +#include "mem.h" +#include "colors.h" +#include "widget.h" +#include "textbox.h" +#include "proc_files.h" + +static struct widget proc_widget; +static ITEM *items[7]; +static unsigned int items_count; +static MENU *menu; + +static void on_menu_key(int key) +{ + static const struct { + int key; + int request; + } key_map[] = { + { KEY_DOWN, REQ_DOWN_ITEM }, + { KEY_UP, REQ_UP_ITEM }, + { KEY_HOME, REQ_FIRST_ITEM }, + { KEY_NPAGE, REQ_SCR_DPAGE }, + { KEY_PPAGE, REQ_SCR_UPAGE }, + { KEY_BEG, REQ_FIRST_ITEM }, + { KEY_END, REQ_LAST_ITEM }, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(key_map); ++i) + if (key_map[i].key == key) { + menu_driver(menu, key_map[i].request); + break; + } +} + +static void on_handle_key(int key) +{ + ITEM *item; + + switch (key) { + case 27: + case KEY_CANCEL: + proc_widget.close(); + break; + case 10: + case 13: + case KEY_ENTER: + item = current_item(menu); + if (item) + show_textfile(item_name(item)); + break; + default: + on_menu_key(key); + break; + } +} + +static bool create(void) +{ + int rows, columns; + const char *title; + + if (screen_lines < 3 || screen_cols < 20) { + proc_widget.close(); + beep(); + return FALSE; + } + scale_menu(menu, &rows, &columns); + rows += 2; + columns += 2; + if (rows > screen_lines) + rows = screen_lines; + if (columns > screen_cols) + columns = screen_cols; + + widget_init(&proc_widget, rows, columns, SCREEN_CENTER, SCREEN_CENTER, + attr_menu, WIDGET_BORDER | WIDGET_SUBWINDOW); + + title = _("Select File"); + mvwprintw(proc_widget.window, 0, (columns - 2 - get_mbs_width(title)) / 2, " %s ", title); + set_menu_win(menu, proc_widget.window); + set_menu_sub(menu, proc_widget.subwindow); + return TRUE; +} + +static void on_window_size_changed(void) +{ + unpost_menu(menu); + if (!create()) + return; + post_menu(menu); +} + +static void on_close(void) +{ + unsigned int i; + + unpost_menu(menu); + free_menu(menu); + for (i = 0; i < items_count; ++i) + free_item(items[i]); + widget_free(&proc_widget); +} + +static void add_item(const char *file_name) +{ + if (access(file_name, F_OK) == 0) { + items[items_count] = new_item(file_name, NULL); + if (!items[items_count]) + fatal_error("cannot create menu item"); + ++items_count; + assert(items_count < ARRAY_SIZE(items)); + } +} + +static struct widget proc_widget = { + .handle_key = on_handle_key, + .window_size_changed = on_window_size_changed, + .close = on_close, +}; + +void create_proc_files_list(void) +{ + items_count = 0; + add_item("/proc/asound/version"); + add_item("/proc/asound/cards"); + add_item("/proc/asound/devices"); + add_item("/proc/asound/oss/devices"); + add_item("/proc/asound/timers"); + add_item("/proc/asound/pcm"); + items[items_count] = NULL; + + menu = new_menu(items); + if (!menu) + fatal_error("cannot create menu"); + set_menu_fore(menu, attr_menu_selected); + set_menu_back(menu, attr_menu); + set_menu_mark(menu, NULL); + menu_opts_off(menu, O_SHOWDESC); + + if (!create()) + return; + + post_menu(menu); +} 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 <timj@gtk.org> + * Jaroslav Kysela <perex@perex.cz> + * Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include CURSESINC +#include <alsa/asoundlib.h> +#include "gettext_curses.h" +#include "utils.h" +#include "die.h" +#include "mem.h" +#include "colors.h" +#include "widget.h" +#include "textbox.h" + +#define MAX_FILE_SIZE 1048576 + +static void create_text_box(const char *const *lines, unsigned int count, + const char *title, int attrs); + +void show_error(const char *msg, int err) +{ + const char *lines[2]; + unsigned int count; + + lines[0] = msg; + count = 1; + if (err) { + lines[1] = strerror(err); + count = 2; + } + create_text_box(lines, count, _("Error"), attr_errormsg); +} + +void show_alsa_error(const char *msg, int err) +{ + const char *lines[2]; + unsigned int count; + + lines[0] = msg; + count = 1; + if (err < 0) { + lines[1] = snd_strerror(err); + count = 2; + } + create_text_box(lines, count, _("Error"), attr_errormsg); +} + +static char *read_file(const char *file_name, unsigned int *file_size) +{ + FILE *f; + int err; + char *buf; + unsigned int allocated = 2048; + unsigned int bytes_read; + + f = fopen(file_name, "r"); + if (!f) { + err = errno; + buf = casprintf(_("Cannot open file \"%s\"."), file_name); + show_error(buf, err); + free(buf); + return NULL; + } + *file_size = 0; + do { + allocated *= 2; + buf = crealloc(buf, allocated); + bytes_read = fread(buf + *file_size, 1, allocated - *file_size, f); + *file_size += bytes_read; + } while (*file_size == allocated && allocated < MAX_FILE_SIZE); + fclose(f); + if (*file_size > 0 && buf[*file_size - 1] != '\n' && *file_size < allocated) { + buf[*file_size] = '\n'; + ++*file_size; + } + return buf; +} + +void show_textfile(const char *file_name) +{ + char *buf; + unsigned int file_size; + unsigned int line_count; + unsigned int i; + const char **lines; + const char *start_line; + + buf = read_file(file_name, &file_size); + if (!buf) + return; + line_count = 0; + for (i = 0; i < file_size; ++i) + line_count += buf[i] == '\n'; + lines = ccalloc(line_count, sizeof *lines); + line_count = 0; + start_line = buf; + for (i = 0; i < file_size; ++i) { + if (buf[i] == '\n') { + lines[line_count++] = start_line; + buf[i] = '\0'; + start_line = &buf[i + 1]; + } + if (buf[i] == '\t') + buf[i] = ' '; + } + create_text_box(lines, line_count, file_name, attr_textbox); + free(lines); + free(buf); +} + +void show_text(const char *const *lines, unsigned int count, const char *title) +{ + create_text_box(lines, count, title, attr_textbox); +} + +/**********************************************************************/ + +static struct widget text_widget; +static char *title; +static int widget_attrs; +static char **text_lines; +static unsigned int text_lines_count; +static int max_line_width; +static int text_box_y; +static int text_box_x; +static int max_scroll_y; +static int max_scroll_x; +static int current_top; +static int current_left; + +static void update_text_lines(void) +{ + int i; + int width; + const char *line_begin; + const char *line_end; + int cur_y, cur_x; + int rest_of_line; + + for (i = 0; i < text_box_y; ++i) { + width = current_left; + line_begin = mbs_at_width(text_lines[current_top + i], &width, 1); + wmove(text_widget.window, i + 1, 1); + if (width > current_left) + waddch(text_widget.window, ' '); + if (*line_begin != '\0') { + width = text_box_x; + line_end = mbs_at_width(line_begin, &width, -1); + if (width) + waddnstr(text_widget.window, line_begin, + line_end - line_begin); + } + getyx(text_widget.window, cur_y, cur_x); + if (cur_y == i + 1) { + rest_of_line = text_box_x + 1 - cur_x; + if (rest_of_line > 0) + wprintw(text_widget.window, "%*s", rest_of_line, ""); + } + } +} + +static void update_y_scroll_bar(void) +{ + int length; + int begin, end; + int i; + + if (max_scroll_y <= 0 || text_lines_count == 0) + return; + length = text_box_y * text_box_y / text_lines_count; + if (length >= text_box_y) + return; + begin = current_top * (text_box_y - length) / max_scroll_y; + end = begin + length; + for (i = 0; i < text_box_y; ++i) + mvwaddch(text_widget.window, i + 1, text_box_x + 1, + i >= begin && i < end ? ACS_BOARD : ' '); +} + +static void update_x_scroll_bar(void) +{ + int length; + int begin, end; + int i; + + if (max_scroll_x <= 0 || max_line_width <= 0) + return; + length = text_box_x * text_box_x / max_line_width; + if (length >= text_box_x) + return; + begin = current_left * (text_box_x - length) / max_scroll_x; + end = begin + length; + wmove(text_widget.window, text_box_y + 1, 1); + for (i = 0; i < text_box_x; ++i) + waddch(text_widget.window, i >= begin && i < end ? ACS_BOARD : ' '); +} + +static void move_x(int delta) +{ + int left; + + left = current_left + delta; + if (left < 0) + left = 0; + else if (left > max_scroll_x) + left = max_scroll_x; + if (left != current_left) { + current_left = left; + update_text_lines(); + update_x_scroll_bar(); + } +} + +static void move_y(int delta) +{ + int top; + + top = current_top + delta; + if (top < 0) + top = 0; + else if (top > max_scroll_y) + top = max_scroll_y; + if (top != current_top) { + current_top = top; + update_text_lines(); + update_y_scroll_bar(); + } +} + +static void on_handle_key(int key) +{ + switch (key) { + case 10: + case 13: + case 27: + case KEY_CANCEL: + case KEY_ENTER: + case KEY_CLOSE: + case KEY_EXIT: + text_widget.close(); + break; + case KEY_DOWN: + case KEY_SF: + case 'J': + case 'j': + case 'X': + case 'x': + move_y(1); + break; + case KEY_UP: + case KEY_SR: + case 'K': + case 'k': + case 'W': + case 'w': + move_y(-1); + break; + case KEY_LEFT: + case 'H': + case 'h': + case 'P': + case 'p': + move_x(-1); + break; + case KEY_RIGHT: + case 'L': + case 'l': + case 'N': + case 'n': + move_x(1); + break; + case KEY_NPAGE: + case ' ': + move_y(text_box_y); + break; + case KEY_PPAGE: + case KEY_BACKSPACE: + case 'B': + case 'b': + move_y(-text_box_y); + break; + case KEY_HOME: + case KEY_BEG: + move_x(-max_scroll_x); + break; + case KEY_LL: + case KEY_END: + move_x(max_scroll_x); + break; + case '\t': + move_x(8); + break; + case KEY_BTAB: + move_x(-8); + break; + } +} + +static bool create(void) +{ + int len, width; + + if (screen_lines < 3 || screen_cols < 8) { + text_widget.close(); + beep(); + return FALSE; + } + + width = max_line_width; + len = get_mbs_width(title) + 2; + if (width < len) + width = len; + + text_box_y = text_lines_count; + if (text_box_y > screen_lines - 2) + text_box_y = screen_lines - 2; + max_scroll_y = text_lines_count - text_box_y; + text_box_x = width; + if (text_box_x > screen_cols - 2) + text_box_x = screen_cols - 2; + max_scroll_x = max_line_width - text_box_x; + + widget_init(&text_widget, text_box_y + 2, text_box_x + 2, + SCREEN_CENTER, SCREEN_CENTER, widget_attrs, WIDGET_BORDER); + mvwprintw(text_widget.window, 0, (text_box_x + 2 - get_mbs_width(title) - 2) / 2, " %s ", title); + + if (current_top > max_scroll_y) + current_top = max_scroll_y; + if (current_left > max_scroll_x) + current_left = max_scroll_x; + update_text_lines(); + update_y_scroll_bar(); + update_x_scroll_bar(); + return TRUE; +} + +static void on_window_size_changed(void) +{ + create(); +} + +static void on_close(void) +{ + unsigned int i; + + for (i = 0; i < text_lines_count; ++i) + free(text_lines[i]); + free(text_lines); + widget_free(&text_widget); +} + +static struct widget text_widget = { + .handle_key = on_handle_key, + .window_size_changed = on_window_size_changed, + .close = on_close, +}; + +static void create_text_box(const char *const *lines, unsigned int count, + const char *title_, int attrs) +{ + unsigned int i; + + text_lines = ccalloc(count, sizeof *text_lines); + for (i = 0; i < count; ++i) + text_lines[i] = cstrdup(lines[i]); + text_lines_count = count; + max_line_width = get_max_mbs_width(lines, count); + title = cstrdup(title_); + widget_attrs = attrs; + + current_top = 0; + current_left = 0; + + create(); +} 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 <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define _XOPEN_SOURCE +#include "aconfig.h" +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <wchar.h> +#include "utils.h" + +/* + * mbs_at_width - compute screen position in a string + * + * For displaying strings on the screen, we have to know how many character + * cells are occupied. This function calculates the position in a multibyte + * string that is at a desired position. + * + * Parameters: + * s: the string + * width: on input, the desired number of character cells; on output, the actual + * position, in character cells, of the return value + * dir: -1 or 1; in which direction to round if a multi-column character goes + * over the desired width + * + * Return value: + * Pointer to the place in the string that is as near the desired width as + * possible. If the string is too short, the return value points to the + * terminating zero. If the last character is a multi-column character that + * goes over the desired width, the return value may be one character cell + * earlier or later than desired, depending on the dir parameter. + * In any case, the return value points after any zero-width characters that + * follow the last character. + */ +const char *mbs_at_width(const char *s, int *width, int dir) +{ + size_t len; + wchar_t wc; + int bytes; + int width_so_far, w; + + if (*width <= 0) + return s; + mbtowc(NULL, NULL, 0); /* reset shift state */ + len = strlen(s); + width_so_far = 0; + while (len && (bytes = mbtowc(&wc, s, len)) > 0) { + w = wcwidth(wc); + if (width_so_far + w > *width && dir < 0) + break; + if (w >= 0) + width_so_far += w; + s += bytes; + len -= bytes; + if (width_so_far >= *width) { + while (len && (bytes = mbtowc(&wc, s, len)) > 0) { + w = wcwidth(wc); + if (w != 0) + break; + s += bytes; + len -= bytes; + } + break; + } + } + *width = width_so_far; + return s; +} + +/* + * get_mbs_width - compute screen width of a string + */ +unsigned int get_mbs_width(const char *s) +{ + int width; + + width = INT_MAX; + mbs_at_width(s, &width, 1); + return width; +} + +/* + * get_max_mbs_width - get width of longest string in an array + */ +unsigned int get_max_mbs_width(const char *const *s, unsigned int count) +{ + unsigned int max_width, i, len; + + max_width = 0; + for (i = 0; i < count; ++i) { + len = get_mbs_width(s[i]); + if (len > max_width) + max_width = len; + } + return max_width; +} 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 <clemens@ladisch.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "aconfig.h" +#include <stdlib.h> +#include <term.h> +#include "die.h" +#include "widget.h" + +int screen_lines; +int screen_cols; + +static int cursor_visibility = -1; + +static void widget_handle_key(int key) +{ +} + +static void update_cursor_visibility(void) +{ + struct widget *active_widget; + + active_widget = get_active_widget(); + if (active_widget && + active_widget->cursor_visibility != cursor_visibility) { + cursor_visibility = active_widget->cursor_visibility; + curs_set(cursor_visibility); + } +} + +void widget_init(struct widget *widget, int lines_, int cols, int y, int x, + chtype bkgd, unsigned int flags) +{ + WINDOW *old_window; + + if (y == SCREEN_CENTER) + y = (screen_lines - lines_) / 2; + if (x == SCREEN_CENTER) + x = (screen_cols - cols) / 2; + + old_window = widget->window; + widget->window = newwin(lines_, cols, y, x); + if (!widget->window) + fatal_error("cannot create window"); + keypad(widget->window, TRUE); + nodelay(widget->window, TRUE); + leaveok(widget->window, !(flags & WIDGET_CURSOR_VISIBLE)); + wbkgdset(widget->window, bkgd); + werase(widget->window); + + if (flags & WIDGET_BORDER) + box(widget->window, 0, 0); + if (flags & WIDGET_SUBWINDOW) { + if (widget->subwindow) + delwin(widget->subwindow); + widget->subwindow = derwin(widget->window, + lines_ - 2, cols - 2, 1, 1); + if (!widget->subwindow) + fatal_error("cannot create subwindow"); + wbkgdset(widget->subwindow, bkgd); + } + widget->cursor_visibility = !!(flags & WIDGET_CURSOR_VISIBLE); + + if (widget->panel) { + replace_panel(widget->panel, widget->window); + } else { + widget->panel = new_panel(widget->window); + if (!widget->panel) + fatal_error("cannot create panel"); + set_panel_userptr(widget->panel, widget); + } + + if (!widget->handle_key) + widget->handle_key = widget_handle_key; + + if (old_window) + delwin(old_window); + + update_cursor_visibility(); +} + +void widget_free(struct widget *widget) +{ + if (widget->panel) { + del_panel(widget->panel); + widget->panel = NULL; + } + if (widget->subwindow) { + delwin(widget->subwindow); + widget->subwindow = NULL; + } + if (widget->window) { + delwin(widget->window); + widget->window = NULL; + } + + update_cursor_visibility(); +} + +struct widget *get_active_widget(void) +{ + PANEL *active_panel; + + active_panel = panel_below(NULL); + if (active_panel) + return panel_userptr(active_panel); + else + return NULL; +} + +void window_size_changed(void) +{ + PANEL *panel, *below; + struct widget *widget; + + getmaxyx(stdscr, screen_lines, screen_cols); + if (tigetflag("xenl") != 1 && tigetflag("am") != 1) + --screen_lines; + + for (panel = panel_below(NULL); panel; panel = below) { + below = panel_below(panel); + widget = panel_userptr(panel); + widget->window_size_changed(); + } +} 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 <panel.h> + +#define WIDGET_BORDER 0x1 +#define WIDGET_SUBWINDOW 0x2 +#define WIDGET_CURSOR_VISIBLE 0x4 + +#define SCREEN_CENTER -1 + +struct widget { + WINDOW *window; + WINDOW *subwindow; /* optional: contents without border */ + PANEL *panel; + int cursor_visibility; + + void (*handle_key)(int key); + void (*window_size_changed)(void); + void (*close)(void); +}; + +extern int screen_lines; +extern int screen_cols; + +void widget_init(struct widget *widget, + int lines_, int cols, int y, int x, + chtype bkgd, unsigned int flags); +void widget_free(struct widget *widget); +struct widget *get_active_widget(void); +void window_size_changed(void); + +#endif |