summaryrefslogtreecommitdiff
path: root/alsamixer
diff options
context:
space:
mode:
authorClemens Ladisch <clemens@ladisch.de>2009-05-25 10:26:22 +0200
committerClemens Ladisch <clemens@ladisch.de>2009-05-25 10:26:22 +0200
commit5b6b5fd14bd5b0f6b07332657fc09b0a7255dda9 (patch)
tree9ab445378b65f81ff362076cff8ffd35947baefd /alsamixer
parentbde1d198c11662b3ce10c4afd87ce67c2e50ae9a (diff)
downloadalsa-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')
-rw-r--r--alsamixer/Makefile.am14
-rw-r--r--alsamixer/README84
-rw-r--r--alsamixer/alsamixer.130
-rw-r--r--alsamixer/alsamixer.c2412
-rw-r--r--alsamixer/card_select.c268
-rw-r--r--alsamixer/card_select.h7
-rw-r--r--alsamixer/cli.c135
-rw-r--r--alsamixer/colors.c103
-rw-r--r--alsamixer/colors.h27
-rw-r--r--alsamixer/device_name.c197
-rw-r--r--alsamixer/device_name.h6
-rw-r--r--alsamixer/die.c39
-rw-r--r--alsamixer/die.h7
-rw-r--r--alsamixer/mainloop.c135
-rw-r--r--alsamixer/mainloop.h10
-rw-r--r--alsamixer/mem.c68
-rw-r--r--alsamixer/mem.h11
-rw-r--r--alsamixer/mixer_controls.c521
-rw-r--r--alsamixer/mixer_controls.h37
-rw-r--r--alsamixer/mixer_display.c739
-rw-r--r--alsamixer/mixer_display.h10
-rw-r--r--alsamixer/mixer_widget.c680
-rw-r--r--alsamixer/mixer_widget.h36
-rw-r--r--alsamixer/proc_files.c169
-rw-r--r--alsamixer/proc_files.h6
-rw-r--r--alsamixer/textbox.c396
-rw-r--r--alsamixer/textbox.h10
-rw-r--r--alsamixer/utils.c111
-rw-r--r--alsamixer/utils.h10
-rw-r--r--alsamixer/widget.c140
-rw-r--r--alsamixer/widget.h33
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(&current_selem_id);
+ if (err < 0)
+ fatal_error("out of memory");
+}
+
+static void set_view_mode(enum view_mode m)
+{
+ view_mode = m;
+ create_controls();
+}
+
+static void close_hctl(void)
+{
+ free_controls();
+ if (mixer_device_name) {
+ snd_mixer_detach(mixer, mixer_device_name);
+ free(mixer_device_name);
+ mixer_device_name = NULL;
+ }
+}
+
+static void check_unplugged(void)
+{
+ snd_hctl_t *hctl;
+ snd_ctl_t *ctl;
+ unsigned int state;
+ int err;
+
+ unplugged = FALSE;
+ if (mixer_device_name) {
+ err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
+ if (err >= 0) {
+ ctl = snd_hctl_ctl(hctl);
+ /* just any random function that does an ioctl() */
+ err = snd_ctl_get_power_state(ctl, &state);
+ if (err == -ENODEV)
+ unplugged = TRUE;
+ }
+ }
+}
+
+void close_mixer_device(void)
+{
+ check_unplugged();
+ close_hctl();
+
+ display_card_info();
+ set_view_mode(view_mode);
+}
+
+bool select_card_by_name(const char *device_name)
+{
+ int err;
+ bool opened;
+ char *msg;
+
+ close_hctl();
+ unplugged = FALSE;
+
+ opened = FALSE;
+ if (device_name) {
+ err = snd_mixer_attach(mixer, device_name);
+ if (err >= 0)
+ opened = TRUE;
+ else {
+ msg = casprintf(_("Cannot open mixer device '%s'."), device_name);
+ show_alsa_error(msg, err);
+ free(msg);
+ }
+ }
+ if (opened) {
+ mixer_device_name = cstrdup(device_name);
+
+ err = snd_mixer_load(mixer);
+ if (err < 0)
+ fatal_alsa_error(_("cannot load mixer controls"), err);
+ }
+
+ display_card_info();
+ set_view_mode(view_mode);
+ return opened;
+}
+
+static void show_help(void)
+{
+ const char *help[] = {
+ _("Esc Exit"),
+ _("F1 ? H Help"),
+ _("F2 / System information"),
+ _("F3 Show playback controls"),
+ _("F4 Show capture controls"),
+ _("F5 Show all controls"),
+ _("Tab Toggle view mode (F3/F4/F5)"),
+ _("F6 S Select sound card"),
+ _("L Redraw screen"),
+ "",
+ _("Left Move to the previous control"),
+ _("Right Move to the next control"),
+ "",
+ _("Up/Down Change volume"),
+ _("+ - Change volume"),
+ _("Page Up/Dn Change volume in big steps"),
+ _("End Set volume to 0%"),
+ _("0-9 Set volume to 0%-90%"),
+ _("Q W E Increase left/both/right volumes"),
+ /* TRANSLATORS: or Y instead of Z */
+ _("Z X C Decrease left/both/right volumes"),
+ _("B Balance left and right volumes"),
+ "",
+ _("M Toggle mute"),
+ /* TRANSLATORS: or , . */
+ _("< > Toggle left/right mute"),
+ "",
+ _("Space Toggle capture"),
+ /* TRANSLATORS: or Insert Delete */
+ _("; ' Toggle left/right capture"),
+ "",
+ _("Authors:"),
+ _(" Tim Janik <timj@gtk.org>"),
+ _(" Jaroslav Kysela <perex@perex.cz>"),
+ _(" Clemens Ladisch <clemens@ladisch.de>"),
+ };
+ show_text(help, ARRAY_SIZE(help), _("Help"));
+}
+
+void refocus_control(void)
+{
+ if (focus_control_index < controls_count) {
+ snd_mixer_selem_get_id(controls[focus_control_index].elem, current_selem_id);
+ current_control_flags = controls[focus_control_index].flags;
+ }
+
+ display_controls();
+}
+
+static struct control *get_focus_control(unsigned int type)
+{
+ if (focus_control_index >= 0 &&
+ focus_control_index < controls_count &&
+ (controls[focus_control_index].flags & IS_ACTIVE) &&
+ (controls[focus_control_index].flags & type))
+ return &controls[focus_control_index];
+ else
+ return NULL;
+}
+
+static void change_enum_to_percent(struct control *control, int value)
+{
+ unsigned int i;
+ unsigned int index;
+ unsigned int new_index;
+ int items;
+ int err;
+
+ i = ffs(control->enum_channel_bits) - 1;
+ err = snd_mixer_selem_get_enum_item(control->elem, i, &index);
+ if (err < 0)
+ return;
+ new_index = index;
+ if (value == 0) {
+ new_index = 0;
+ } else if (value == 100) {
+ items = snd_mixer_selem_get_enum_items(control->elem);
+ if (items < 1)
+ return;
+ new_index = items - 1;
+ }
+ if (new_index == index)
+ return;
+ for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
+ if (control->enum_channel_bits & (1 << i))
+ snd_mixer_selem_set_enum_item(control->elem, i, new_index);
+}
+
+static void change_enum_relative(struct control *control, int delta)
+{
+ int items;
+ unsigned int i;
+ unsigned int index;
+ int new_index;
+ int err;
+
+ items = snd_mixer_selem_get_enum_items(control->elem);
+ if (items < 1)
+ return;
+ err = snd_mixer_selem_get_enum_item(control->elem, 0, &index);
+ if (err < 0)
+ return;
+ new_index = (int)index + delta;
+ if (new_index < 0)
+ new_index = 0;
+ else if (new_index >= items)
+ new_index = items - 1;
+ if (new_index == index)
+ return;
+ for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
+ if (control->enum_channel_bits & (1 << i))
+ snd_mixer_selem_set_enum_item(control->elem, i, new_index);
+}
+
+static void change_volume_to_percent(struct control *control, int value, unsigned int channels)
+{
+ int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
+ int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
+ long min, max;
+ int err;
+
+ if (!(control->flags & HAS_VOLUME_1))
+ channels = LEFT;
+ if (control->flags & TYPE_PVOLUME) {
+ get_range_func = snd_mixer_selem_get_playback_volume_range;
+ set_func = snd_mixer_selem_set_playback_volume;
+ } else {
+ get_range_func = snd_mixer_selem_get_capture_volume_range;
+ set_func = snd_mixer_selem_set_capture_volume;
+ }
+ err = get_range_func(control->elem, &min, &max);
+ if (err < 0)
+ return;
+ if (channels & LEFT)
+ set_func(control->elem, control->volume_channels[0], min + (max - min) * value / 100);
+ if (channels & RIGHT)
+ set_func(control->elem, control->volume_channels[1], min + (max - min) * value / 100);
+}
+
+static void change_volume_relative(struct control *control, int delta, unsigned int channels)
+{
+ int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
+ int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
+ int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
+ long min, max;
+ long left, right;
+ long value;
+ int err;
+
+ if (!(control->flags & HAS_VOLUME_1))
+ channels = LEFT;
+ if (control->flags & TYPE_PVOLUME) {
+ get_range_func = snd_mixer_selem_get_playback_volume_range;
+ get_func = snd_mixer_selem_get_playback_volume;
+ set_func = snd_mixer_selem_set_playback_volume;
+ } else {
+ get_range_func = snd_mixer_selem_get_capture_volume_range;
+ get_func = snd_mixer_selem_get_capture_volume;
+ set_func = snd_mixer_selem_set_capture_volume;
+ }
+ err = get_range_func(control->elem, &min, &max);
+ if (err < 0)
+ return;
+ if (channels & LEFT) {
+ err = get_func(control->elem, control->volume_channels[0], &left);
+ if (err < 0)
+ return;
+ }
+ if (channels & RIGHT) {
+ err = get_func(control->elem, control->volume_channels[1], &right);
+ if (err < 0)
+ return;
+ }
+ if (channels & LEFT) {
+ value = left + delta;
+ if (value < min)
+ value = min;
+ else if (value > max)
+ value = max;
+ if (value != left)
+ set_func(control->elem, control->volume_channels[0], value);
+ }
+ if (channels & RIGHT) {
+ value = right + delta;
+ if (value < min)
+ value = min;
+ else if (value > max)
+ value = max;
+ if (value != right)
+ set_func(control->elem, control->volume_channels[1], value);
+ }
+}
+
+static void change_control_to_percent(int value, unsigned int channels)
+{
+ struct control *control;
+
+ control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
+ if (!control)
+ return;
+ if (control->flags & TYPE_ENUM)
+ change_enum_to_percent(control, value);
+ else
+ change_volume_to_percent(control, value, channels);
+ display_controls();
+}
+
+static void change_control_relative(int delta, unsigned int channels)
+{
+ struct control *control;
+
+ control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
+ if (!control)
+ return;
+ if (control->flags & TYPE_ENUM)
+ change_enum_relative(control, delta);
+ else
+ change_volume_relative(control, delta, channels);
+ display_controls();
+}
+
+static void toggle_switches(unsigned int type, unsigned int channels)
+{
+ struct control *control;
+ unsigned int switch_1_mask;
+ int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *);
+ int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int);
+ snd_mixer_selem_channel_id_t channel_ids[2];
+ int left, right;
+ int err;
+
+ control = get_focus_control(type);
+ if (!control)
+ return;
+ if (type == TYPE_PSWITCH) {
+ switch_1_mask = HAS_PSWITCH_1;
+ get_func = snd_mixer_selem_get_playback_switch;
+ set_func = snd_mixer_selem_set_playback_switch;
+ channel_ids[0] = control->pswitch_channels[0];
+ channel_ids[1] = control->pswitch_channels[1];
+ } else {
+ switch_1_mask = HAS_CSWITCH_1;
+ get_func = snd_mixer_selem_get_capture_switch;
+ set_func = snd_mixer_selem_set_capture_switch;
+ channel_ids[0] = control->cswitch_channels[0];
+ channel_ids[1] = control->cswitch_channels[1];
+ }
+ if (!(control->flags & switch_1_mask))
+ channels = LEFT;
+ if (channels & LEFT) {
+ err = get_func(control->elem, channel_ids[0], &left);
+ if (err < 0)
+ return;
+ }
+ if (channels & RIGHT) {
+ err = get_func(control->elem, channel_ids[1], &right);
+ if (err < 0)
+ return;
+ }
+ if (channels & LEFT)
+ set_func(control->elem, channel_ids[0], !left);
+ if (channels & RIGHT)
+ set_func(control->elem, channel_ids[1], !right);
+ display_controls();
+}
+
+static void toggle_mute(unsigned int channels)
+{
+ toggle_switches(TYPE_PSWITCH, channels);
+}
+
+static void toggle_capture(unsigned int channels)
+{
+ toggle_switches(TYPE_CSWITCH, channels);
+}
+
+static void balance_volumes(void)
+{
+ struct control *control;
+ long left, right;
+ int err;
+
+ control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME);
+ if (!control || !(control->flags & HAS_VOLUME_1))
+ return;
+ if (control->flags & TYPE_PVOLUME) {
+ err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[0], &left);
+ if (err < 0)
+ return;
+ err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[1], &right);
+ if (err < 0)
+ return;
+ } else {
+ err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[0], &left);
+ if (err < 0)
+ return;
+ err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[1], &right);
+ if (err < 0)
+ return;
+ }
+ left = (left + right) / 2;
+ if (control->flags & TYPE_PVOLUME) {
+ snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[0], left);
+ snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[1], left);
+ } else {
+ snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[0], left);
+ snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[1], left);
+ }
+ display_controls();
+}
+
+static void on_handle_key(int key)
+{
+ switch (key) {
+ case 27:
+ case KEY_CANCEL:
+ case KEY_F(10):
+ mixer_widget.close();
+ break;
+ case KEY_F(1):
+ case KEY_HELP:
+ case 'H':
+ case 'h':
+ case '?':
+ show_help();
+ break;
+ case KEY_F(2):
+ case '/':
+ create_proc_files_list();
+ break;
+ case KEY_F(3):
+ set_view_mode(VIEW_MODE_PLAYBACK);
+ break;
+ case KEY_F(4):
+ set_view_mode(VIEW_MODE_CAPTURE);
+ break;
+ case KEY_F(5):
+ set_view_mode(VIEW_MODE_ALL);
+ break;
+ case '\t':
+ set_view_mode((enum view_mode)((view_mode + 1) % VIEW_MODE_COUNT));
+ break;
+ case KEY_F(6):
+ case 'S':
+ case 's':
+ create_card_select_list();
+ break;
+ case KEY_REFRESH:
+ case 12:
+ case 'L':
+ case 'l':
+ clearok(mixer_widget.window, TRUE);
+ display_controls();
+ break;
+ case KEY_LEFT:
+ case 'P':
+ case 'p':
+ if (focus_control_index > 0) {
+ --focus_control_index;
+ refocus_control();
+ }
+ break;
+ case KEY_RIGHT:
+ case 'N':
+ case 'n':
+ if (focus_control_index < controls_count - 1) {
+ ++focus_control_index;
+ refocus_control();
+ }
+ break;
+ case KEY_PPAGE:
+ change_control_relative(5, LEFT | RIGHT);
+ break;
+ case KEY_NPAGE:
+ change_control_relative(-5, LEFT | RIGHT);
+ break;
+#if 0
+ case KEY_BEG:
+ case KEY_HOME:
+ change_control_to_percent(100, LEFT | RIGHT);
+ break;
+#endif
+ case KEY_LL:
+ case KEY_END:
+ change_control_to_percent(0, LEFT | RIGHT);
+ break;
+ case KEY_UP:
+ case '+':
+ case 'K':
+ case 'k':
+ case 'W':
+ case 'w':
+ change_control_relative(1, LEFT | RIGHT);
+ break;
+ case KEY_DOWN:
+ case '-':
+ case 'J':
+ case 'j':
+ case 'X':
+ case 'x':
+ change_control_relative(-1, LEFT | RIGHT);
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ change_control_to_percent((key - '0') * 10, LEFT | RIGHT);
+ break;
+ case 'Q':
+ case 'q':
+ change_control_relative(1, LEFT);
+ break;
+ case 'Y':
+ case 'y':
+ case 'Z':
+ case 'z':
+ change_control_relative(-1, LEFT);
+ break;
+ case 'E':
+ case 'e':
+ change_control_relative(1, RIGHT);
+ break;
+ case 'C':
+ case 'c':
+ change_control_relative(-1, RIGHT);
+ break;
+ case 'M':
+ case 'm':
+ toggle_mute(LEFT | RIGHT);
+ break;
+ case 'B':
+ case 'b':
+ case '=':
+ balance_volumes();
+ break;
+ case '<':
+ case ',':
+ toggle_mute(LEFT);
+ break;
+ case '>':
+ case '.':
+ toggle_mute(RIGHT);
+ break;
+ case ' ':
+ toggle_capture(LEFT | RIGHT);
+ break;
+ case KEY_IC:
+ case ';':
+ toggle_capture(LEFT);
+ break;
+ case KEY_DC:
+ case '\'':
+ toggle_capture(RIGHT);
+ break;
+ }
+}
+
+static void create(void)
+{
+ static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " ";
+
+ widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0,
+ attr_mixer_frame, WIDGET_BORDER);
+ if (screen_cols >= (sizeof(title) - 1) + 2) {
+ wattrset(mixer_widget.window, attr_mixer_active);
+ mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title);
+ }
+ init_mixer_layout();
+ display_card_info();
+ set_view_mode(view_mode);
+}
+
+static void on_window_size_changed(void)
+{
+ create();
+}
+
+static void on_close(void)
+{
+ widget_free(&mixer_widget);
+}
+
+void mixer_shutdown(void)
+{
+ free_controls();
+ if (mixer)
+ snd_mixer_close(mixer);
+ if (current_selem_id)
+ snd_mixer_selem_id_free(current_selem_id);
+}
+
+struct widget mixer_widget = {
+ .handle_key = on_handle_key,
+ .window_size_changed = on_window_size_changed,
+ .close = on_close,
+};
+
+void create_mixer_widget(void)
+{
+ create();
+}
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