diff options
Diffstat (limited to 'alsa-utils/alsamixer/alsamixer.c')
-rw-r--r-- | alsa-utils/alsamixer/alsamixer.c | 2408 |
1 files changed, 2408 insertions, 0 deletions
diff --git a/alsa-utils/alsamixer/alsamixer.c b/alsa-utils/alsamixer/alsamixer.c new file mode 100644 index 0000000..414033e --- /dev/null +++ b/alsa-utils/alsamixer/alsamixer.c @@ -0,0 +1,2408 @@ +/* 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 'w': + case 'W': + mixer_vscroll_delta -= 1; + break; + case KEY_DOWN: + 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: + mixer_set_delta(1); + case 'W': + mixer_add_delta(1); + break; + case 'x': + case KEY_DOWN: + 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); +} |