mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-11-10 04:15:44 +01:00
30b608001f
The volume bar and space sizes are recalculated at each time when the view mode is changed. This will fix the problem when the view with fewer volumes is switched to the view with more volumes.
2152 lines
55 KiB
C
2152 lines
55 KiB
C
/* AlsaMixer - Commandline mixer for the ALSA project Copyright (C) 1998,
|
|
* 1999 Tim Janik <timj@gtk.org> and Jaroslav Kysela <perex@suse.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@suse.cz>
|
|
*
|
|
* * ported to the latest mixer 0.9.x API (function based)
|
|
*
|
|
* Fri Jun 23 14:10:00 MEST 2000 Jaroslav Kysela <perex@suse.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@suse.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!).
|
|
*/
|
|
|
|
#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_MIN_Y (MIXER_TEXT_Y + 3) /* abs minimum: 11 */
|
|
|
|
#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 char card_id[64] = "default";
|
|
static snd_mixer_t *mixer_handle;
|
|
static char mixer_card_name[128];
|
|
static char mixer_device_name[128];
|
|
|
|
/* 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_CAPTURE,
|
|
MIXER_ELEM_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 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_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_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 / 1 show Help screen\n"
|
|
" F2 / 2 show /proc info screen\n"
|
|
" F3 / 3 show Playback controls only\n"
|
|
" F4 / 4 show Capture controls only\n"
|
|
" F5 / 5 show all controls\n"
|
|
" Return return to main screen\n"
|
|
" Space toggle Capture facility\n"
|
|
" Tab toggle ExactMode\n"
|
|
" m M mute both channels\n"
|
|
" < > mute 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@suse.cz>.\n"
|
|
);
|
|
|
|
|
|
/* --- draw contexts --- */
|
|
enum {
|
|
DC_DEFAULT,
|
|
DC_BACK,
|
|
DC_TEXT,
|
|
DC_PROMPT,
|
|
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 ('M', DC_CBAR_MUTE, MIXER_CYAN, MIXER_BLACK, A_BOLD);
|
|
mixer_init_dc (ACS_HLINE, DC_CBAR_NOMUTE, MIXER_CYAN, MIXER_BLACK, 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_CBAR_FRAME (DC_CBAR_MUTE)
|
|
#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 + 10 < mixer_max_y)
|
|
y = mixer_max_y / 2 + 3;
|
|
else
|
|
y = (mixer_max_y + 1) / 2 + 3;
|
|
y += mixer_cbar_height / 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);
|
|
}
|
|
|
|
/* update enum list */
|
|
static void update_enum_list(snd_mixer_elem_t *elem, int chn, int delta)
|
|
{
|
|
int eidx;
|
|
if (snd_mixer_selem_get_enum_item(elem, chn, &eidx) < 0)
|
|
return;
|
|
if (delta < 0) {
|
|
eidx--;
|
|
if (eidx < 0)
|
|
return;
|
|
} 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_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 (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 && snd_mixer_selem_has_playback_switch(elem)) {
|
|
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) {
|
|
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_balance_volumes = 0;
|
|
}
|
|
|
|
|
|
static void display_enum_list(snd_mixer_elem_t *elem, int y, int x)
|
|
{
|
|
int i, cury, ch, err;
|
|
|
|
/* clear */
|
|
mixer_dc(DC_TEXT);
|
|
for (i = mixer_cbar_height + 3, cury = y; i > 0; i--, cury--)
|
|
mvaddstr(cury, x, " ");
|
|
|
|
cury = y - 4;
|
|
for (ch = 0; ch < 2; ch++) {
|
|
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
|
|
mixer_update_cbar (int elem_index)
|
|
{
|
|
char string[128], string1[64], *suffix;
|
|
int dc;
|
|
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, i;
|
|
int swl, swr;
|
|
|
|
/* 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) {
|
|
int xlen = mixer_max_x - 8;
|
|
if (xlen > 63)
|
|
xlen = 63;
|
|
mixer_dc (DC_PROMPT);
|
|
mvaddstr (3, 2, "View: ");
|
|
mixer_dc (DC_TEXT);
|
|
switch (mixer_view) {
|
|
case VIEW_PLAYBACK:
|
|
mvaddstr (3, 8, "Playback");
|
|
break;
|
|
case VIEW_CAPTURE:
|
|
mvaddstr (3, 8, "Capture");
|
|
break;
|
|
default:
|
|
mvaddstr (3, 8, "All");
|
|
break;
|
|
}
|
|
mixer_dc (DC_PROMPT);
|
|
mvaddstr (4, 2, "Item: ");
|
|
mixer_dc (DC_TEXT);
|
|
string1[8] = 0;
|
|
for (i = 0; i < 63; i++)
|
|
string1[i] = ' ';
|
|
string1[xlen] = '\0';
|
|
strcpy(string, snd_mixer_selem_id_get_name(sid));
|
|
if (mixer_type[elem_index] & MIXER_ELEM_CAPTURE_SUFFIX)
|
|
strcat(string, " Capture");
|
|
if (snd_mixer_selem_id_get_index(sid) > 0)
|
|
sprintf(string + strlen(string), " %i", snd_mixer_selem_id_get_index(sid));
|
|
if ((mixer_type[elem_index] & MIXER_ELEM_MUTE_SWITCH)
|
|
&& snd_mixer_selem_has_playback_switch(elem)) {
|
|
snd_mixer_selem_get_playback_switch(elem, chn_left, &swl);
|
|
swr = 0;
|
|
if (chn_right != SND_MIXER_SCHN_UNKNOWN)
|
|
snd_mixer_selem_get_playback_switch(elem, chn_right, &swr);
|
|
if (! swl && ! swr)
|
|
sprintf(string + strlen(string), " [Off]");
|
|
}
|
|
string[xlen] = '\0';
|
|
strncpy(string1, string, strlen(string));
|
|
addstr(string1);
|
|
}
|
|
|
|
/* get channel bar position
|
|
*/
|
|
if (!mixer_cbar_get_pos (elem_index, &x, &y))
|
|
return;
|
|
|
|
/* channel bar name
|
|
*/
|
|
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)
|
|
sprintf(string1, "%s%s %d", snd_mixer_selem_id_get_name(sid), suffix, snd_mixer_selem_id_get_index(sid));
|
|
else
|
|
sprintf(string1, "%s%s", snd_mixer_selem_id_get_name(sid), suffix);
|
|
string1[8] = 0;
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
string[i] = ' ';
|
|
}
|
|
sprintf (string + (8 - strlen (string1)) / 2, "%s ", string1);
|
|
string[8] = 0;
|
|
mvaddstr (y, x, string);
|
|
y--;
|
|
|
|
/* current channel values
|
|
*/
|
|
mixer_dc (DC_BACK);
|
|
mvaddstr (y, x, " ");
|
|
if (mixer_type[elem_index] & MIXER_ELEM_HAS_VOLUME) {
|
|
mixer_dc (DC_TEXT);
|
|
if (chn_right == SND_MIXER_SCHN_UNKNOWN) {
|
|
/* mono */
|
|
sprintf (string, "%ld", vleft);
|
|
mvaddstr (y, x + 4 - strlen (string) / 2, string);
|
|
} else {
|
|
/* stereo */
|
|
sprintf (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);
|
|
sprintf (string, "%ld", vright);
|
|
mvaddstr (y, x + 5, string);
|
|
}
|
|
}
|
|
y--;
|
|
|
|
if (type == MIXER_ELEM_ENUM) {
|
|
display_enum_list(elem, y, x);
|
|
return;
|
|
}
|
|
|
|
/* left/right bar
|
|
*/
|
|
mixer_dc (DC_CBAR_FRAME);
|
|
mvaddstr (y, x, " ");
|
|
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);
|
|
}
|
|
string[2] = 0;
|
|
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--;
|
|
}
|
|
|
|
/* muted?
|
|
*/
|
|
mixer_dc (DC_BACK);
|
|
mvaddstr (y, x, " ");
|
|
if ((mixer_type[elem_index] & MIXER_ELEM_MUTE_SWITCH)
|
|
&& snd_mixer_selem_has_playback_switch(elem)) {
|
|
mixer_dc (DC_CBAR_FRAME);
|
|
mvaddch (y, x + 2, ACS_ULCORNER);
|
|
snd_mixer_selem_get_playback_switch(elem, chn_left, &swl);
|
|
dc = swl ? DC_CBAR_NOMUTE : DC_CBAR_MUTE;
|
|
mvaddch (y, x + 3, mixer_dc (dc));
|
|
if (chn_right != SND_MIXER_SCHN_UNKNOWN) {
|
|
snd_mixer_selem_get_playback_switch(elem, chn_right, &swr);
|
|
dc = swr ? DC_CBAR_NOMUTE : DC_CBAR_MUTE;
|
|
}
|
|
mvaddch (y, x + 4, mixer_dc (dc));
|
|
mixer_dc (DC_CBAR_FRAME);
|
|
mvaddch (y, x + 5, ACS_URCORNER);
|
|
} else {
|
|
mixer_dc (DC_CBAR_FRAME);
|
|
mvaddch (y, x + 2, ACS_ULCORNER);
|
|
mvaddch (y, x + 3, ACS_HLINE);
|
|
mvaddch (y, x + 4, ACS_HLINE);
|
|
mvaddch (y, x + 5, ACS_URCORNER);
|
|
}
|
|
y--;
|
|
|
|
/* capture input?
|
|
*/
|
|
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);
|
|
if (swl || (has_r_sw && swr)) {
|
|
mixer_dc (DC_CBAR_CAPTURE);
|
|
mvaddstr (y, x + 1, "CAPTUR");
|
|
if (swl) {
|
|
mvaddstr (y + 1, x + 1, "L");
|
|
if (! has_r_sw)
|
|
mvaddstr (y + 1, x + 6, "R");
|
|
}
|
|
if (has_r_sw && swr)
|
|
mvaddstr (y + 1, x + 6, "R");
|
|
} else {
|
|
for (i = 0; i < 6; i++)
|
|
mvaddch (y, x + 1 + i, mixer_dc (DC_CBAR_NOCAPTURE));
|
|
}
|
|
} else {
|
|
mixer_dc (DC_BACK);
|
|
mvaddstr (y, x, " ");
|
|
}
|
|
y--;
|
|
}
|
|
|
|
static void
|
|
mixer_update_cbars (void)
|
|
{
|
|
static int o_x = 0;
|
|
static int o_y = 0;
|
|
int i, x, y;
|
|
|
|
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;
|
|
|
|
mixer_dc (DC_FRAME);
|
|
|
|
/* 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_PROMPT);
|
|
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
|
|
*/
|
|
mixer_dc (DC_PROMPT);
|
|
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);
|
|
}
|
|
|
|
/* 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 ((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, 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));
|
|
if (MIXER_TEXT_Y + 10 < mixer_max_y)
|
|
mixer_cbar_height = 10 + MAX (0, mixer_max_y - MIXER_TEXT_Y - 10 ) / 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 *)malloc(sizeof(int) * mixer_n_elems);
|
|
if (mixer_type == NULL)
|
|
mixer_abort(ERR_FCN, "malloc", 0);
|
|
if (mixer_grpidx)
|
|
free(mixer_grpidx);
|
|
mixer_grpidx = (int *)malloc(sizeof(int) * mixer_n_elems);
|
|
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 = 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) {
|
|
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)) {
|
|
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;
|
|
}
|
|
elem_index++;
|
|
nelems_added++;
|
|
if (elem_index >= mixer_n_elems)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (mixer_view == VIEW_CAPTURE) || (mixer_view == VIEW_CHANNELS) ) {
|
|
if (snd_mixer_selem_has_capture_volume(elem) ||
|
|
(nelems_added == 0 && snd_mixer_selem_has_capture_switch(elem))) {
|
|
mixer_grpidx[elem_index] = idx;
|
|
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 */
|
|
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):
|
|
case '1':
|
|
mixer_view = VIEW_HELP;
|
|
key = 0;
|
|
break;
|
|
case '/':
|
|
case KEY_F (2):
|
|
case '2':
|
|
mixer_view = VIEW_PROCINFO;
|
|
key = 0;
|
|
break;
|
|
case KEY_F (3):
|
|
case '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):
|
|
case '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):
|
|
case '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 '\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 '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);
|
|
|
|
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, sys_siglist[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:shg");
|
|
switch (opt)
|
|
{
|
|
case '?':
|
|
case 'h':
|
|
fprintf (stderr, "%s v%s\n", PRGNAME_UPPER, VERSION);
|
|
fprintf (stderr, "Usage: %s [-h] [-c <card: 0...7 or id>] [-D <mixer device>] [-g] [-s]\n", PRGNAME);
|
|
mixer_abort (ERR_NONE, "", 0);
|
|
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;
|
|
}
|
|
}
|
|
while (opt > 0);
|
|
|
|
/* initialize mixer
|
|
*/
|
|
mixer_init ();
|
|
mixer_reinit ();
|
|
|
|
if (mixer_n_view_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);
|
|
};
|