1
0
Fork 0

android-16.0
Some checks failed
Build alsa-utils / fedora_latest_build (push) Has been cancelled
Build alsa-utils / ubuntu_last_build (push) Has been cancelled

This commit is contained in:
dvab-sarma 2025-06-26 23:48:13 -05:00
commit d2fc7afa59
300 changed files with 84772 additions and 0 deletions

30
alsamixer/Makefile.am Normal file
View file

@ -0,0 +1,30 @@
AM_CFLAGS = -D_GNU_SOURCE @CURSES_CFLAGS@ -DCURSESINC="@CURSESINC@"
LDADD = @CURSESLIB@
bin_PROGRAMS = alsamixer
alsamixer_SOURCES = card_select.c card_select.h \
bindings.c bindings.h \
cli.c \
colors.c colors.h \
curskey.c curskey.h \
configparser.c configparser.h \
device_name.c device_name.h \
die.c die.h \
mainloop.c mainloop.h \
mem.c mem.h \
menu_widget.c menu_widget.h \
mixer_clickable.c mixer_clickable.h \
mixer_controls.c mixer_controls.h \
mixer_display.c mixer_display.h \
mixer_widget.c mixer_widget.h \
proc_files.c proc_files.h \
textbox.c textbox.h \
utils.c utils.h \
volume_mapping.c volume_mapping.h \
widget.c widget.h
man_MANS = alsamixer.1
EXTRA_DIST = alsamixer.1 alsamixer.rc.example
alsamixer_CPPFLAGS = -I$(top_srcdir)/include
#LDFLAGS = -static
#CFLAGS += -g -Wall

400
alsamixer/alsamixer.1 Normal file
View file

@ -0,0 +1,400 @@
.TH ALSAMIXER 1 "22 May 2009"
.SH NAME
alsamixer \- soundcard mixer for ALSA soundcard driver, with ncurses interface
.SH SYNOPSIS
\fBalsamixer\fP [\fIoptions\fP]
.SH DESCRIPTION
\fBalsamixer\fP is an ncurses mixer program for use with the ALSA
soundcard drivers. It supports multiple soundcards with multiple devices.
.SH OPTIONS
.TP
\fI\-h, \-\-help\fP
Help: show available flags.
.TP
\fI\-c, \-\-card\fP <card number or identification>
Select the soundcard to use, if you have more than one. Cards are
numbered from 0 (the default).
.TP
\fI\-D, \-\-device\fP <device identification>
Select the mixer device to control.
.TP
\fI\-V, \-\-view\fP <mode>
Select the starting view mode, either \fIplayback\fP, \fIcapture\fP or \fIall\fP.
.TP
\fI\-B, \-\-black-background\fP
Use the black background color.
.TP
\fI\-g, \-\-no\-color\fP
Toggle the using of colors.
.SH MIXER VIEWS
The top-left corner of \fBalsamixer\fP shows some basic
information: the card name, the mixer chip name, the current view
mode and the currently selected mixer item.
When the mixer item is switched off, \fI[Off]\fP is displayed in its
name.
Volume bars are located below the basic information area. You can
scroll left/right when all controls can't be put in a single screen.
The name of each control is shown in the bottom below the volume bars.
The currently selected item is drawn in red and/or emphasized.
Each mixer control with volume capability shows a box and the current
volume filled in that box. The volume percentages are displayed below
the volume bar for left and right channels. For a mono control, only
one value is shown there.
When a mixer control is turned off, \fIM\fP (mute) appears below the
volume bar. When it's turned on, \fIO\fP in green appears instead.
You can toggle the switch via \fIm\fP key.
When a mixer control has capture capability, the capture flag appears
below the volume bar, too. When the capture is turned off,
\-\-\-\-\-\-\- is shown. \fICAPTURE\fP in red appears when the
capture switch is turned on. In addition, \fIL\fP and \fIR\fP letters
appear in left and right side to indicate that left and the right
channels are turned on.
Some controls have the enumeration list, and don't show boxes but only
texts which indicate the currently active item. You can change the
item via up/down keys.
.SH VIEW MODES
\fBalsamixer\fP has three view modes: playback, capture and all.
In the playback view, only the controls related with playback are shown.
Similarly, only the controls for capture (recording) are shown in the capture
view. The all view mode shows all controls. The current view mode is displayed
in the top-left position together with the mixer name, etc.
The default view mode is the playback view. You can change it via
\fI-V\fP option.
Each view mode can be switched via keyboard commands, too.
See the next section.
.SH KEYBOARD COMMANDS
\fBalsamixer\fP recognizes the following keyboard commands to control the soundcard.
Commands shown here in upper case can also be given in lower case.
To be reminded of these keystrokes, hit the \fIh\fP key.
.SS
General Controls
The \fILeft\fP and \fIright arrow\fP keys are used to select the
channel (or device, depending on your preferred terminology). You can
also use \fIn\fP ("next") and \fIp\fP ("previous").
The \fIUp\fP and \fIDown Arrows\fP control the volume for the
currently selected device. You can also use \fI+\fP or \fI\-\fP for the
same purpose. Both the left and right signals are affected. For
independent left and right control, see below.
The \fIB\fP or \fI=\fP key adjusts the balance of volumes on left and
right channels.
\fIM\fP toggles muting for the current channel (both left and right).
If the hardware supports it, you can
mute left and right independently by using \fI,\fP (or \fI<\fP) and
\fI.\fP (or \fI>\fP) respectively.
\fISPACE\fP enables recording for the current channel. If any other
channels have recording enabled, they will have their recording function
disabled first. This only works for valid input channels, of course.
\fIL\fP re-draws the screen.
.SS
View Mode Controls
Function keys are used to change view modes.
You can switch to the help mode and the proc info mode via \fIF1\fP and
\fIF2\fP keys, respectively.
On terminals that can't use function keys like gnome\-terminal, \fI?\fP and
\fI/\fP keys can be used alternatively for help and proc modes.
\fIF3\fP, \fIF4\fP and \fIF5\fP keys are used to switch to playback, capture
and all view mode, respectively. \fITAB\fP key toggles the
current view mode circularly.
.SS
Quick Volume Changes
\fIPageUp\fP increases volume by 5.
\fIPageDown\fP decreases volume by 5.
\fIEnd\fP sets volume to 0.
You can also control left & right levels for the current channel
independently, as follows:
[\fIQ\fP | \fIW\fP | \fIE\fP ] -- turn UP [ left | both | right ]
[\fIZ\fP | \fIX\fP | \fIC\fP ] -- turn DOWN [ left | both | right ]
If the currently selected mixer channel is not a stereo channel, then
all UP keys will work like \fIW\fP, and all DOWN keys will work like \fIX\fP.
The number keys from \fI0\fP to \fI9\fP are to change the absolute volume
quickly. They correspond to 0 to 90% volume.
.SS
Selecting the Sound Card
You can select another sound card by pressing the \fIF6\fP or \fIS\fP keys.
This will show a list of available sound cards to choose from,
and an entry to enter the mixer device name by hand.
.SS
Exiting
Quit the program with \fIALT Q\fP, or by hitting \fIESC\fP.
Please note that you might need to hit \fIESC\fP twice on some terminals
since it's regarded as a prefix key.
.SH VOLUME MAPPING
In \fBalsamixer\fP, the volume is mapped to a value that is more natural
for a human ear. The mapping is designed so that the position in the
interval is proportional to the volume as a human ear would perceive
it, i.e. the position is the cubic root of the linear sample
multiplication factor. For controls with a small range (24 dB or
less), the mapping is linear in the dB values so that each step has
the same size visually.
Only for controls without dB information, a linear mapping of the
hardware volume register values is used (this is the same algorithm as
used in the old \fBalsamixer\fP).
.SH FILES
Configuration is read from the following files:
\fI$XDG_CONFIG_HOME\fP/alsamixer.rc
\fI$HOME\fP/.config/alsamixer.rc
.SH CONFIGURATION
Comments start with '#'.
.TP
\fBcolor\fP \fIelement\fP \fIforeground\fP \fIbackground\fP [\fIattribute...\fP]
Valid values for \fIforeground\fP and \fIbackground\fP are: red, green, yellow, blue, magenta, cyan, white, black, none / default.
Valid values for \fIattribute\fP are: bold, normal, reverse, underline, dim, italic, blink.
See section \fBTHEME ELEMENTS\fP for a list of \fIelements\fP.
.TP
\fBset\fP \fIoption\fP \fIvalue\fP
\fBmouse_wheel_step\fP \fI<N>\fP
Set the mouse wheel step to \fI<N>\fP
\fBmouse_wheel_focuses_control\fP \fI0|1\fP
If enabled (\fI1\fP), mixer controls can be changed by hovering over them and scrolling the mouse wheel.
\fBbackground\fP \fIcolor\fP
Set the default background color
.TP
\fBbind\fP \fIkey_definition\fP \fIcommand\fP
A \fIkey_definition\fP may be:
- a single character
- a combination with control: C-a
- a combination with alt: M-a
- a special keyname: Enter, Escape, PageUp, PageDown, Left, Down, Right, Up, Home, End, Backspace, F1 .. F12
See section \fBCOMMANDS\fP for a list of \fIcommands\fP.
.SS COMMANDS
.TP
\fItextbox_close\fP
Close textbox
.TP
\fItextbox_up\fP / \fItextbox_down\fP
Scroll text up/down by one line
.TP
\fItextbox_left\fP / \fItextbox_right\fP
Scroll text left/right by one column
.TP
\fItextbox_page_up\fP / \fItextbox_page_down\fP
Scroll text up/down one page
.TP
\fItextbox_page_left\fP / \fItextbox_page_right\fP
Scroll text left/right one page
.TP
\fItextbox_top\fP / \fItextbox_bottom\fP
Go to top/bottom position of text
.TP
\fImixer_balance_volumes\fP
Balance both channels
.TP
\fImixer_close\fP
Close alsamixer application
.TP
\fImixer_help\fP
Show help
.TP
\fImixer_mode_playback\fP
Show playback controls
.TP
\fImixer_mode_capture\fP
Show capture controls
.TP
\fImixer_mode_all\fP
Show playback and capture controls
.TP
\fImixer_mode_toggle\fP
Toggle between playback/capture/all controls
.TP
\fImixer_refresh\fP
Refresh screen
.TP
\fImixer_previous\fP / \fImixer_next\fP
Select previous/next mixer control
.TP
\fImixer_control_focus_<N>\fP
Focus control number \fI<N>\fP.
.TP
\fImixer_control_set_<N>\fP[\fI_left\fP|\fI_right\fP]
Change volume of current control to \fI<N>\fP percent.
.TP
\fImixer_control_up_<N>\fP[\fI_left\fP|\fI_right\fP]
Increase volume of current control by \fI<N>\fP percent.
.TP
\fImixer_control_down_<N>\fP[\fI_left\fP|\fI_right\fP]
Decrease volume of current control by \fI<N>\fP percent.
.TP
\fImixer_toggle_mute\fP[\fI_left\fP|\fI_right\fP]
Toggle mute state of both/left/right channels
.TP
\fImixer_toggle_capture\fP[\fI_left\fP|\fI_right\fP]
Toggle capture state of both/left/right channels
.TP
\fImixer_select_card\fP
Show window for selecting sound card
.TP
\fImixer_system_information\fP
Show system information
.SS THEME ELEMENTS
.TP
\fImixer_frame\fP Color of the box around the mixer
.TP
\fImixer_text\fP Color of text at the top of the mixer
.TP
\fImixer_active\fP Color of \fIAlsaMixer\fP heading
.TP
\fIctl_frame\fP Color of the frame around the mixer controls
.TP
\fIctl_mute\fP Color indicating the mute state (\fIMM\fP)
.TP
\fIctl_nomute\fP Color indicating the unmute state (\fI00\fP)
.TP
\fIctl_capture\fP Color of \fICAPTURE\fP text
.TP
\fIctl_nocapture\fP Colof of disabled capture label
.TP
\fIctl_label\fP Color of label underneath mixer controls
.TP
\fIctl_label_focus\fP Color of label underneath focused mixer control
.TP
\fIctl_mark_focus\fP Color of \fI<>\fP marks beside focused mixer label
.TP
\fIctl_bar_lo\fP Color of lower volume bar
.TP
\fIctl_bar_mi\fP Color of middle volume bar
.TP
\fIctl_bar_hi\fP Color of top volume bar
.TP
\fIctl_inactive\fP Color for inactive control
.TP
\fIctl_label_inactive\fP Color for inactive label
.TP
\fIerrormsg\fP Color used for error message
.TP
\fIinfomsg\fP Color used for information message
.TP
\fItextbox\fP Color used for textbox (user input)
.TP
\fItextfield\fP Color used for textbox
.TP
\fImenu\fP Color used for menu
.TP
\fImenu_selected\fP Color used for selected entry in menu
.SH SEE ALSO
\fB
amixer(1),
aplay(1),
arecord(1)
\fP
.SH AUTHOR
.B alsamixer
has been written by Tim Janik and
been further improved by Jaroslav Kysela <perex@perex.cz>
and Clemens Ladisch <clemens@ladisch.de>.
This manual page was provided by Paul Winkler <zarmzarm@erols.com>.

View file

@ -0,0 +1,192 @@
### alsamixer configuration file
#
# location: ~/alsamixer.rc or ~/.config/alsamixer.rc or ~/.alsamixer.rc
### color configuration
# color <element> <fg-color> <bg-color> [<attr> ...]
#
# color:
# default, black, red, green, yellow, blue, magenta, cyan, white
#
# attr:
# bold, reverse, standout, dim, underline, italic, normal, blink
#
# element:
# mixer_frame, mixer_text, mixer_active, ctl_frame, ctl_mute, ctl_nomute,
# ctl_capture, ctl_nocapture, ctl_label, ctl_label_focus, ctl_mark_focus,
# ctl_bar_lo, ctl_bar_mi, ctl_bar_hi, ctl_inactive, ctl_label_inactive,
# errormsg, infomsg, textbox, textfield, menu, menu_selected
#color mixer_frame cyan default
#color mixer_text cyan default
#color mixer_active yellow default bold
#color ctl_frame cyan default bold
#color ctl_mute cyan default
#color ctl_nomute white green bold
#color ctl_capture red default bold
#color ctl_nocapture white default
#color ctl_label white blue bold
#color ctl_label_focus red blue bold
#color ctl_mark_focus red default bold
#color ctl_bar_lo green green bold
#color ctl_bar_mi white white bold
#color ctl_bar_hi red red bold
#color ctl_inactive white default
#color ctl_label_inactive white default reverse
#color errormsg white red bold
#color infomsg white blue bold
#color textbox white blue bold
#color textfield white default reverse
#color menu white blue bold
#color menu_selected white blue reverse
### key bindings
# bind <key> [mixer] <mixer-cmd>
# bind <key> textbox <textbox-cmd>
#
# mixer-cmd:
# help, close, refresh, select_card, system_information, mode_all,
# mode_capture, mode_playback, mode_toggle, balance_control, next[_<N>],
# previous[_<N>], control_focus_<[_left|_right]N>,
# control_up[_<N>][_left|_right], control_down[_<N>][_left|_right],
# control_set_<N>[_left|_right], toggle_mute[_left|_right],
# toggle_capture[_left|_right]
#
# textbox-cmd:
# close, down, up, left, right, page_down, page_up, page_left, page_right,
# top, bottom
#
# key: a key definition may be:
# - a single character (A, a, Z, z, ...)
# - a character with control-modifier (^x, C-x, c-x, ...)
# - a character with meta/alt-modifier (M-x, m-x, A-x, a-x, ...)
# - a character with both modifiers (C-M-x, M-C-x, M-^x, ...)
# - a curses keyname, no modifiers allowed (KEY_HOME, Home, F1, F(1), ...)
#bind Escape mixer close
#bind Cancel mixer close
#bind F10 mixer close
#bind F1 mixer help
#bind Help mixer help
#bind H mixer help
#bind h mixer help
#bind ? mixer help
#bind F2 mixer system_information
#bind / mixer system_information
#bind F3 mixer mode_playback
#bind F4 mixer mode_capture
#bind F5 mixer mode_all
#bind Tab mixer mode_toggle
#bind F6 mixer select_card
#bind S mixer select_card
#bind s mixer select_card
#bind Refresh mixer refresh
#bind ^L mixer refresh
#bind L mixer refresh
#bind l mixer refresh
#bind Left mixer previous
#bind P mixer previous
#bind p mixer previous
#bind Right mixer next
#bind N mixer next
#bind n mixer next
#bind PPage mixer control_up_5
#bind NPage mixer control_down_5
#bind LL mixer control_set_0
#bind END mixer control_set_0
#bind 0 mixer control_set_0
#bind 1 mixer control_set_10
#bind 2 mixer control_set_20
#bind 3 mixer control_set_30
#bind 4 mixer control_set_40
#bind 5 mixer control_set_50
#bind 6 mixer control_set_60
#bind 7 mixer control_set_70
#bind 8 mixer control_set_80
#bind 9 mixer control_set_90
#bind Up mixer control_up
#bind + mixer control_up
#bind K mixer control_up
#bind k mixer control_up
#bind W mixer control_up
#bind w mixer control_up
#bind Down mixer control_down
#bind - mixer control_down
#bind J mixer control_down
#bind j mixer control_down
#bind X mixer control_down
#bind x mixer control_down
#bind Q mixer control_up_left
#bind q mixer control_up_left
#bind Y mixer control_down_left
#bind y mixer control_down_left
#bind Z mixer control_down_left
#bind z mixer control_down_left
#bind E mixer control_up_right
#bind e mixer control_up_right
#bind C mixer control_down_right
#bind c mixer control_down_right
#bind M mixer toggle_mute
#bind m mixer toggle_mute
#bind < mixer toggle_mute_left
#bind , mixer toggle_mute_left
#bind > mixer toggle_mute_right
#bind . mixer toggle_mute_right
#bind Space mixer toggle_capture
#bind IC mixer toggle_capture_left
#bind ; mixer toggle_capture_left
#bind DC mixer toggle_capture_right
#bind ' mixer toggle_capture_right
#bind B mixer balance_control
#bind b mixer balance_control
#bind = mixer balance_control
#bind Escape textbox close
#bind Cancel textbox close
#bind Enter textbox close
#bind Close textbox close
#bind Exit textbox close
#bind Down textbox down
#bind SF textbox down
#bind J textbox down
#bind j textbox down
#bind X textbox down
#bind x textbox down
#bind Up textbox up
#bind SR textbox up
#bind K textbox up
#bind k textbox up
#bind W textbox up
#bind w textbox up
#bind Left textbox left
#bind H textbox left
#bind h textbox left
#bind P textbox left
#bind p textbox left
#bind Right textbox right
#bind L textbox right
#bind l textbox right
#bind N textbox right
#bind n textbox right
#bind NPage textbox page_down
#bind SPace textbox page_down
#bind PPage textbox page_up
#bind Backspace textbox page_up
#bind B textbox page_up
#bind b textbox page_up
#bind Home textbox top
#bind Beg textbox top
#bind LL textbox bottom
#bind End textbox bottom
#bind Tab textbox page_right
#bind BTab textbox page_left
### misc configuration
# set mouse_wheel_step [0..100]
# set mouse_wheel_focuses_control [0|1]
#set mouse_wheel_step 1
#set mouse_wheel_focuses_control 1

132
alsamixer/bindings.c Normal file
View file

@ -0,0 +1,132 @@
#include "bindings.h"
#include "mixer_widget.h"
#define CNTRL(C) (C%32)
uint8_t textbox_bindings[KEY_MAX] = {
['\n'] = CMD_TEXTBOX_CLOSE,
['\r'] = CMD_TEXTBOX_CLOSE,
[27] = CMD_TEXTBOX_CLOSE,
[KEY_CANCEL] = CMD_TEXTBOX_CLOSE,
[KEY_ENTER] = CMD_TEXTBOX_CLOSE,
[KEY_CLOSE] = CMD_TEXTBOX_CLOSE,
[KEY_EXIT] = CMD_TEXTBOX_CLOSE,
[KEY_DOWN] = CMD_TEXTBOX_DOWN,
[KEY_SF] = CMD_TEXTBOX_DOWN,
['J'] = CMD_TEXTBOX_DOWN,
['j'] = CMD_TEXTBOX_DOWN,
['X'] = CMD_TEXTBOX_DOWN,
['x'] = CMD_TEXTBOX_DOWN,
[KEY_UP] = CMD_TEXTBOX_UP,
[KEY_SR] = CMD_TEXTBOX_UP,
['K'] = CMD_TEXTBOX_UP,
['k'] = CMD_TEXTBOX_UP,
['W'] = CMD_TEXTBOX_UP,
['w'] = CMD_TEXTBOX_UP,
[KEY_LEFT] = CMD_TEXTBOX_LEFT,
['H'] = CMD_TEXTBOX_LEFT,
['h'] = CMD_TEXTBOX_LEFT,
['P'] = CMD_TEXTBOX_LEFT,
['p'] = CMD_TEXTBOX_LEFT,
[KEY_RIGHT] = CMD_TEXTBOX_RIGHT,
['L'] = CMD_TEXTBOX_RIGHT,
['l'] = CMD_TEXTBOX_RIGHT,
['N'] = CMD_TEXTBOX_RIGHT,
['n'] = CMD_TEXTBOX_RIGHT,
[KEY_NPAGE] = CMD_TEXTBOX_PAGE_DOWN,
[' '] = CMD_TEXTBOX_PAGE_DOWN,
[KEY_PPAGE] = CMD_TEXTBOX_PAGE_UP,
[KEY_BACKSPACE] = CMD_TEXTBOX_PAGE_UP,
['B'] = CMD_TEXTBOX_PAGE_UP,
['b'] = CMD_TEXTBOX_PAGE_UP,
[KEY_HOME] = CMD_TEXTBOX_TOP,
[KEY_BEG] = CMD_TEXTBOX_TOP,
[KEY_LL] = CMD_TEXTBOX_BOTTOM,
[KEY_END] = CMD_TEXTBOX_BOTTOM,
['\t'] = CMD_TEXTBOX_PAGE_RIGHT,
[KEY_BTAB] = CMD_TEXTBOX_PAGE_LEFT,
};
command_enum mixer_bindings[] = {
[27] = CMD_MIXER_CLOSE,
[KEY_CANCEL] = CMD_MIXER_CLOSE,
[KEY_F(10)] = CMD_MIXER_CLOSE,
[KEY_F(1)] = CMD_MIXER_HELP,
[KEY_HELP] = CMD_MIXER_HELP,
['H'] = CMD_MIXER_HELP,
['h'] = CMD_MIXER_HELP,
['?'] = CMD_MIXER_HELP,
[KEY_F(2)] = CMD_MIXER_SYSTEM_INFORMATION,
['/'] = CMD_MIXER_SYSTEM_INFORMATION,
[KEY_F(3)] = CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_PLAYBACK),
[KEY_F(4)] = CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_CAPTURE),
[KEY_F(5)] = CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_ALL),
['\t'] = CMD_MIXER_TOGGLE_VIEW_MODE,
[KEY_F(6)] = CMD_MIXER_SELECT_CARD,
['S'] = CMD_MIXER_SELECT_CARD,
['s'] = CMD_MIXER_SELECT_CARD,
[KEY_REFRESH] = CMD_MIXER_REFRESH,
[CNTRL('L')] = CMD_MIXER_REFRESH,
['L'] = CMD_MIXER_REFRESH,
['l'] = CMD_MIXER_REFRESH,
[KEY_LEFT] = CMD_WITH_ARG(CMD_MIXER_PREVIOUS, 1),
['p'] = CMD_WITH_ARG(CMD_MIXER_PREVIOUS, 1),
['P'] = CMD_WITH_ARG(CMD_MIXER_PREVIOUS, 1),
[KEY_RIGHT] = CMD_WITH_ARG(CMD_MIXER_NEXT, 1),
['N'] = CMD_WITH_ARG(CMD_MIXER_NEXT, 1),
['n'] = CMD_WITH_ARG(CMD_MIXER_NEXT, 1),
[KEY_PPAGE] = CMD_WITH_ARG(CMD_MIXER_CONTROL_UP, 5),
[KEY_NPAGE] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN, 5),
[KEY_LL] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 0),
[KEY_END] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 0),
['0'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 0),
['1'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 10),
['2'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 20),
['3'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 30),
['4'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 40),
['5'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 50),
['6'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 60),
['7'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 70),
['8'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 80),
['9'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 90),
#if 0
[KEY_BEG] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 100),
[KEY_HELP] = CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT, 100),
#endif
[KEY_UP] = CMD_WITH_ARG(CMD_MIXER_CONTROL_UP, 1),
['+'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_UP, 1),
['K'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_UP, 1),
['k'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_UP, 1),
['W'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_UP, 1),
['w'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_UP, 1),
[KEY_DOWN] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN, 1),
['-'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN, 1),
['J'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN, 1),
['j'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN, 1),
['X'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN, 1),
['x'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN, 1),
['Q'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_UP_LEFT, 1),
['q'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_UP_LEFT, 1),
['Y'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN_LEFT, 1),
['y'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN_LEFT, 1),
['Z'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN_LEFT, 1),
['z'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN_LEFT, 1),
['E'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_UP_RIGHT, 1),
['e'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_UP_RIGHT, 1),
['C'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN_RIGHT, 1),
['c'] = CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN_RIGHT, 1),
['M'] = CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, LEFT|RIGHT),
['m'] = CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, LEFT|RIGHT),
['<'] = CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, LEFT),
[','] = CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, LEFT),
['>'] = CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, RIGHT),
['.'] = CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, RIGHT),
[' '] = CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, LEFT|RIGHT),
[KEY_IC] = CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, LEFT),
[';'] = CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, LEFT),
[KEY_DC] = CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, RIGHT),
['\''] = CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, RIGHT),
['B'] = CMD_MIXER_BALANCE_CONTROL,
['b'] = CMD_MIXER_BALANCE_CONTROL,
['='] = CMD_MIXER_BALANCE_CONTROL,
};

81
alsamixer/bindings.h Normal file
View file

@ -0,0 +1,81 @@
#ifndef BINDINGS_H_INCLUDED
#define BINDINGS_H_INCLUDED
#include CURSESINC
#include <menu.h>
#include <stdint.h>
/* Commands are stored in an uint16_t and may take an unsigned numeric argument.
* The command itself is stored in the lower 7 bits, the argument is stored
* in the higher 9 bits.
*
* The value `0` is used for no (unbound) command. */
typedef uint16_t command_enum;
extern command_enum mixer_bindings[KEY_MAX];
/* No need for a 16bit type, since textbox commands don't take arguments */
extern uint8_t textbox_bindings[KEY_MAX];
#define CMD_WITH_ARG(CMD, ARG) \
((CMD) + ((ARG) << 9))
#define CMD_GET_COMMAND(CMD) \
((CMD) & 0x1FF)
#define CMD_GET_ARGUMENT(CMD) \
((CMD) >> 9)
enum mixer_command {
// `CMD % 4` should produce the channel mask
CMD_MIXER_CONTROL_DOWN_LEFT = 1,
CMD_MIXER_CONTROL_DOWN_RIGHT,
CMD_MIXER_CONTROL_DOWN,
CMD_MIXER_CONTROL_UP_LEFT = 5,
CMD_MIXER_CONTROL_UP_RIGHT,
CMD_MIXER_CONTROL_UP,
CMD_MIXER_CONTROL_SET_PERCENT_LEFT = 9,
CMD_MIXER_CONTROL_SET_PERCENT_RIGHT,
CMD_MIXER_CONTROL_SET_PERCENT,
// Keep those in the same order as displayed on screen
CMD_MIXER_HELP,
CMD_MIXER_SYSTEM_INFORMATION,
CMD_MIXER_SELECT_CARD,
CMD_MIXER_CLOSE,
CMD_MIXER_TOGGLE_VIEW_MODE,
CMD_MIXER_SET_VIEW_MODE,
CMD_MIXER_PREVIOUS,
CMD_MIXER_NEXT,
CMD_MIXER_FOCUS_CONTROL,
CMD_MIXER_TOGGLE_MUTE,
CMD_MIXER_TOGGLE_CAPTURE,
CMD_MIXER_BALANCE_CONTROL,
CMD_MIXER_REFRESH,
// Mouse
CMD_MIXER_MOUSE_CLICK_MUTE,
CMD_MIXER_MOUSE_CLICK_VOLUME_BAR,
CMD_MIXER_MOUSE_CLICK_CONTROL_ENUM,
};
enum textbox_command {
/* Since these commands are also used by the menu widget we make use of
* the menu_driver() request constants.
* KEY_MAX is substracted so the value fits in 8 bits. */
CMD_TEXTBOX___MIN_MENU_COMMAND = MIN_MENU_COMMAND - KEY_MAX,
CMD_TEXTBOX_TOP = REQ_FIRST_ITEM - KEY_MAX,
CMD_TEXTBOX_BOTTOM = REQ_LAST_ITEM - KEY_MAX,
CMD_TEXTBOX_LEFT = REQ_LEFT_ITEM - KEY_MAX,
CMD_TEXTBOX_RIGHT = REQ_RIGHT_ITEM - KEY_MAX,
CMD_TEXTBOX_UP = REQ_UP_ITEM - KEY_MAX,
CMD_TEXTBOX_DOWN = REQ_DOWN_ITEM - KEY_MAX,
CMD_TEXTBOX_PAGE_DOWN = REQ_SCR_DPAGE - KEY_MAX,
CMD_TEXTBOX_PAGE_UP = REQ_SCR_UPAGE - KEY_MAX,
CMD_TEXTBOX___MAX_MENU_COMMAND = MAX_MENU_COMMAND - KEY_MAX,
CMD_TEXTBOX_PAGE_LEFT,
CMD_TEXTBOX_PAGE_RIGHT,
CMD_TEXTBOX_CLOSE,
};
#endif

201
alsamixer/card_select.c Normal file
View file

@ -0,0 +1,201 @@
/*
* card_select.c - select a card by list or device name
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <alsa/asoundlib.h>
#include <menu.h>
#include "gettext_curses.h"
#include "die.h"
#include "mem.h"
#include "utils.h"
#include "colors.h"
#include "widget.h"
#include "menu_widget.h"
#include "mixer_widget.h"
#include "device_name.h"
#include "card_select.h"
struct card {
struct card *next;
char *indexstr;
char *name;
char *device_name;
};
static struct widget list_widget;
static struct card first_card;
static ITEM **items;
static MENU *menu;
static ITEM *initial_item;
static void on_key_enter(void)
{
ITEM *item = current_item(menu);
if (item) {
struct card *card = item_userptr(item);
if (card->device_name) {
if (select_card_by_name(card->device_name))
list_widget.close();
} else {
create_device_name_form();
}
}
}
static void on_handle_key(int key)
{
switch (menu_widget_handle_key(menu, key)) {
case KEY_ENTER:
on_key_enter();
break;
case KEY_CANCEL:
list_widget.close();
break;
}
}
static void create(void)
{
menu_widget_create(&list_widget, menu, _("Sound Card"));
}
void close_card_select_list(void)
{
unsigned int i;
struct card *card, *next_card;
unpost_menu(menu);
free_menu(menu);
for (i = 0; items[i]; ++i)
free_item(items[i]);
free(items);
for (card = first_card.next; card; card = next_card) {
next_card = card->next;
free(card->indexstr);
free(card->name);
free(card->device_name);
free(card);
}
widget_free(&list_widget);
}
static struct widget list_widget = {
.handle_key = on_handle_key,
.window_size_changed = create,
.close = close_card_select_list,
};
static int get_cards(void)
{
int count, number, err;
snd_ctl_t *ctl;
snd_ctl_card_info_t *info;
char buf[32];
struct card *card, *prev_card;
first_card.indexstr = "-";
first_card.name = _("(default)");
first_card.device_name = "default";
count = 1;
snd_ctl_card_info_alloca(&info);
prev_card = &first_card;
number = -1;
for (;;) {
err = snd_card_next(&number);
if (err < 0)
fatal_alsa_error(_("cannot enumerate sound cards"), err);
if (number < 0)
break;
#if defined(SND_LIB_VER) && SND_LIB_VER(1, 2, 5) <= SND_LIB_VERSION
sprintf(buf, "sysdefault:%d", number);
#else
sprintf(buf, "hw:%d", number);
#endif
err = snd_ctl_open(&ctl, buf, 0);
if (err < 0)
continue;
err = snd_ctl_card_info(ctl, info);
snd_ctl_close(ctl);
if (err < 0)
continue;
card = ccalloc(1, sizeof *card);
card->device_name = cstrdup(buf);
card->indexstr = cstrdup(buf + 3);
card->name = cstrdup(snd_ctl_card_info_get_name(info));
prev_card->next = card;
prev_card = card;
++count;
}
card = ccalloc(1, sizeof *card);
card->indexstr = cstrdup(" ");
card->name = cstrdup(_("enter device name..."));
prev_card->next = card;
++count;
return count;
}
static void create_list_items(int cards)
{
int i;
struct card *card;
ITEM *item;
initial_item = NULL;
items = ccalloc(cards + 1, sizeof(ITEM*));
i = 0;
for (card = &first_card; card; card = card->next) {
item = new_item(card->indexstr, card->name);
if (!item)
fatal_error("cannot create menu item");
set_item_userptr(item, card);
items[i++] = item;
if (!initial_item &&
mixer_device_name &&
(!card->device_name ||
!strcmp(card->device_name, mixer_device_name)))
initial_item = item;
}
assert(i == cards);
}
void create_card_select_list(void)
{
int cards;
cards = get_cards();
create_list_items(cards);
menu = new_menu(items);
if (!menu)
fatal_error("cannot create menu");
set_menu_fore(menu, attrs.menu_selected);
set_menu_back(menu, attrs.menu);
set_menu_mark(menu, NULL);
if (initial_item)
set_current_item(menu, initial_item);
set_menu_spacing(menu, 2, 1, 1);
menu_opts_on(menu, O_SHOWDESC);
create();
}

7
alsamixer/card_select.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef CARD_SELECT_H_INCLUDED
#define CARD_SELECT_H_INCLUDED
void create_card_select_list(void);
void close_card_select_list(void);
#endif

180
alsamixer/cli.c Normal file
View file

@ -0,0 +1,180 @@
/*
* alsamixer - curses mixer for the ALSA project
* Copyright (c) 1998,1999 Tim Janik
* Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <getopt.h>
#include <alsa/asoundlib.h>
#include "gettext_curses.h"
#include "mixer_widget.h"
#include "mainloop.h"
#include "configparser.h"
#include "colors.h"
static int black_background = 0;
static int use_color = 1;
static int use_mouse = 1;
static const char* config_file = CONFIG_DEFAULT;
static struct snd_mixer_selem_regopt selem_regopt = {
.ver = 1,
.abstract = SND_MIXER_SABSTRACT_NONE,
.device = "default",
};
static void show_help(void)
{
puts(_("Usage: alsamixer [options]"));
puts(_("Useful options:\n"
" -h, --help this help\n"
" -c, --card=NUMBER sound card number or id\n"
" -D, --device=NAME mixer device name\n"
" -m, --mouse enable mouse\n"
" -M, --no-mouse disable mouse\n"
" -f, --config=FILE configuration file\n"
" -F, --no-config do not load configuration file\n"
" -V, --view=MODE starting view mode: playback/capture/all\n"
" -B, --black-background use black background color"));
puts(_("Debugging options:\n"
" -g, --no-color toggle using of colors\n"
" -a, --abstraction=NAME mixer abstraction level: none/basic"));
}
static void parse_options(int argc, char *argv[])
{
static const char short_options[] = "hc:f:FD:mMV:Bga:";
static const struct option long_options[] = {
{ .name = "help", .val = 'h' },
{ .name = "card", .has_arg = 1, .val = 'c' },
{ .name = "config", .has_arg = 1, .val = 'f' },
{ .name = "no-config", .val = 'F' },
{ .name = "device", .has_arg = 1, .val = 'D' },
{ .name = "mouse", .val = 'm' },
{ .name = "no-mouse", .val = 'M' },
{ .name = "view", .has_arg = 1, .val = 'V' },
{ .name = "black-background", .val = 'B' },
{ .name = "no-color", .val = 'g' },
{ .name = "abstraction", .has_arg = 1, .val = 'a' },
{ 0 }
};
int option;
int card_index;
static char name_buf[24];
while ((option = getopt_long(argc, argv, short_options,
long_options, NULL)) != -1) {
switch (option) {
case '?':
case 'h':
show_help();
exit(EXIT_SUCCESS);
case 'c':
card_index = snd_card_get_index(optarg);
if (card_index < 0) {
fprintf(stderr, _("invalid card index: %s\n"), optarg);
goto fail;
}
#if defined(SND_LIB_VER) && SND_LIB_VER(1, 2, 5) <= SND_LIB_VERSION
sprintf(name_buf, "sysdefault:%d", card_index);
#else
sprintf(name_buf, "hw:%d", card_index);
#endif
selem_regopt.device = name_buf;
break;
case 'D':
selem_regopt.device = optarg;
break;
case 'f':
config_file = optarg;
break;
case 'F':
config_file = NULL;
break;
case 'm':
use_mouse = 1;
break;
case 'M':
use_mouse = 0;
break;
case 'V':
if (*optarg == 'p' || *optarg == 'P')
view_mode = VIEW_MODE_PLAYBACK;
else if (*optarg == 'c' || *optarg == 'C')
view_mode = VIEW_MODE_CAPTURE;
else
view_mode = VIEW_MODE_ALL;
break;
case 'B':
black_background = 1;
break;
case 'g':
use_color = !use_color;
break;
case 'a':
if (!strcmp(optarg, "none"))
selem_regopt.abstract = SND_MIXER_SABSTRACT_NONE;
else if (!strcmp(optarg, "basic"))
selem_regopt.abstract = SND_MIXER_SABSTRACT_BASIC;
else {
fprintf(stderr, _("unknown abstraction level: %s\n"), optarg);
goto fail;
}
break;
default:
fprintf(stderr, _("unknown option: %c\n"), option);
fail:
fputs(_("try `alsamixer --help' for more information\n"), stderr);
exit(EXIT_FAILURE);
}
}
}
int main(int argc, char *argv[])
{
if (!isatty(fileno(stdin)))
return 0;
setlocale(LC_ALL, "");
#ifdef ENABLE_NLS_IN_CURSES
textdomain(PACKAGE);
#endif
parse_options(argc, argv);
create_mixer_object(&selem_regopt);
initialize_curses(use_color, use_mouse);
if (config_file == CONFIG_DEFAULT)
parse_default_config_file();
else if (config_file)
parse_config_file(config_file);
if (black_background)
reinit_colors(COLOR_BLACK);
create_mixer_widget();
mainloop();
app_shutdown();
return 0;
}

133
alsamixer/colors.c Normal file
View file

@ -0,0 +1,133 @@
/*
* colors.c - color and attribute definitions
* Copyright (c) 1998,1999 Tim Janik
* Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include CURSESINC
#include "colors.h"
struct attributes attrs;
static short background_color = -1;
int get_color_pair(short fg, short bg)
{
static int color_pairs_defined = 0;
short i, pair_fg, pair_bg;
for (i = 1; i <= color_pairs_defined; ++i) {
if (OK == pair_content(i, &pair_fg, &pair_bg))
if (pair_fg == fg && pair_bg == bg)
return COLOR_PAIR(i);
}
if (color_pairs_defined + 1 < COLOR_PAIRS) {
++color_pairs_defined;
init_pair(color_pairs_defined, fg, bg);
return COLOR_PAIR(color_pairs_defined);
}
return 0;
}
void reinit_colors(short bg)
{
if (bg == background_color)
return;
init_pair(1, COLOR_CYAN, bg);
init_pair(2, COLOR_YELLOW, bg);
init_pair(4, COLOR_RED, bg);
init_pair(5, COLOR_WHITE, bg);
background_color = bg;
}
void init_colors(int use_color)
{
if (!!has_colors() == !!use_color) {
start_color();
use_default_colors();
get_color_pair(COLOR_CYAN, background_color); // COLOR_PAIR(1)
get_color_pair(COLOR_YELLOW, background_color);
get_color_pair(COLOR_WHITE, COLOR_GREEN);
get_color_pair(COLOR_RED, background_color);
get_color_pair(COLOR_WHITE, background_color);
get_color_pair(COLOR_WHITE, COLOR_BLUE);
get_color_pair(COLOR_RED, COLOR_BLUE);
get_color_pair(COLOR_GREEN, COLOR_GREEN);
get_color_pair(COLOR_WHITE, COLOR_RED); // COLOR_PAIR(9)
#ifdef TRICOLOR_VOLUME_BAR
get_color_pair(COLOR_WHITE, COLOR_WHITE);
get_color_pair(COLOR_RED, COLOR_RED);
#endif
attrs = (struct attributes) {
.mixer_frame = COLOR_PAIR(1),
.mixer_text = COLOR_PAIR(1),
.mixer_active = A_BOLD | COLOR_PAIR(2),
.ctl_frame = A_BOLD | COLOR_PAIR(1),
.ctl_mute = COLOR_PAIR(1),
.ctl_nomute = A_BOLD | COLOR_PAIR(3),
.ctl_capture = A_BOLD | COLOR_PAIR(4),
.ctl_nocapture = COLOR_PAIR(5),
.ctl_label = A_BOLD | COLOR_PAIR(6),
.ctl_label_focus = A_BOLD | COLOR_PAIR(7),
.ctl_mark_focus = A_BOLD | COLOR_PAIR(4),
.ctl_bar_lo = A_BOLD | COLOR_PAIR(8),
#ifdef TRICOLOR_VOLUME_BAR
.ctl_bar_mi = A_BOLD | COLOR_PAIR(10),
.ctl_bar_hi = A_BOLD | COLOR_PAIR(11),
#endif
.ctl_inactive = COLOR_PAIR(5),
.ctl_label_inactive = A_REVERSE | COLOR_PAIR(5),
.errormsg = A_BOLD | COLOR_PAIR(9),
.infomsg = A_BOLD | COLOR_PAIR(6),
.textbox = A_BOLD | COLOR_PAIR(6),
.textfield = A_REVERSE | COLOR_PAIR(5),
.menu = A_BOLD | COLOR_PAIR(6),
.menu_selected = A_REVERSE | COLOR_PAIR(6)
};
} else {
attrs = (struct attributes) {
.mixer_frame = A_NORMAL,
.mixer_text = A_NORMAL,
.mixer_active = A_BOLD,
.ctl_frame = A_BOLD,
.ctl_mute = A_NORMAL,
.ctl_nomute = A_BOLD,
.ctl_capture = A_BOLD,
.ctl_nocapture = A_NORMAL,
.ctl_label = A_REVERSE,
.ctl_label_focus = A_REVERSE | A_BOLD,
.ctl_mark_focus = A_BOLD,
.ctl_bar_lo = A_BOLD,
#ifdef TRICOLOR_VOLUME_BAR
.ctl_bar_mi = A_BOLD,
.ctl_bar_hi = A_BOLD,
#endif
.ctl_inactive = A_NORMAL,
.ctl_label_inactive = A_REVERSE,
.errormsg = A_STANDOUT,
.infomsg = A_NORMAL,
.textbox = A_NORMAL,
.textfield = A_REVERSE,
.menu = A_NORMAL,
.menu_selected = A_REVERSE,
};
}
}

42
alsamixer/colors.h Normal file
View file

@ -0,0 +1,42 @@
#ifndef COLORS_H_INCLUDED
#define COLORS_H_INCLUDED
#define TRICOLOR_VOLUME_BAR
struct attributes {
// Alphabetically sorted
#ifdef TRICOLOR_VOLUME_BAR
int ctl_bar_hi;
#endif
int ctl_bar_lo;
#ifdef TRICOLOR_VOLUME_BAR
int ctl_bar_mi;
#endif
int ctl_capture;
int ctl_frame;
int ctl_inactive;
int ctl_label;
int ctl_label_focus;
int ctl_label_inactive;
int ctl_mark_focus;
int ctl_mute;
int ctl_nocapture;
int ctl_nomute;
int errormsg;
int infomsg;
int menu;
int menu_selected;
int mixer_active;
int mixer_frame;
int mixer_text;
int textbox;
int textfield;
};
extern struct attributes attrs;
void init_colors(int use_color);
void reinit_colors(short bg);
int get_color_pair(short fg, short bg);
#endif

601
alsamixer/configparser.c Normal file
View file

@ -0,0 +1,601 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include <ctype.h>
#include <errno.h>
#include <pwd.h>
#include CURSESINC
#include "colors.h"
#include "gettext_curses.h"
#include "utils.h"
#include "curskey.h"
#include "bindings.h"
#include "mixer_widget.h"
#define ERROR_CONFIG (-1)
#define ERROR_MISSING_ARGUMENTS (-2)
#define ERROR_TOO_MUCH_ARGUMENTS (-3)
static const char *error_message;
static const char *error_cause;
static int strlist_index(const char *haystack, unsigned int itemlen, const char *needle) {
unsigned int needle_len;
unsigned int pos;
const char *found;
needle_len = strlen(needle);
if (needle_len <= itemlen && needle[needle_len - 1] != ' ') {
found = strstr(haystack, needle);
if (found) {
pos = (found - haystack);
if (pos % itemlen == 0 && (needle_len == itemlen || haystack[pos+needle_len] == ' '))
return pos / itemlen;
}
}
return -1;
}
static int color_by_name(const char *name) {
return strlist_index(
"default"
"black "
"red "
"green "
"yellow "
"blue "
"magenta"
"cyan "
"white ", 7, name) - 1;
};
static int attr_by_name(const char *name) {
return (int[]) {
-1,
A_BOLD,
A_REVERSE,
A_STANDOUT,
A_DIM,
A_UNDERLINE,
#ifdef A_ITALIC
A_ITALIC,
#endif
A_NORMAL,
A_BLINK,
}[strlist_index(
"bold "
"reverse "
"standout "
"dim "
"underline"
#ifdef A_ITALIC
"italic "
#endif
"normal "
"blink ", 9, name) + 1];
};
#define W_NUMBER (1U << 0)
enum textbox_word {
TW_BOTTOM = (1U << 1),
TW_CLOSE = (1U << 2),
TW_DOWN = (1U << 3),
TW_LEFT = (1U << 4),
TW_PAGE = (1U << 5),
TW_RIGHT = (1U << 6),
TW_TOP = (1U << 7),
TW_UP = (1U << 8),
};
const char *textbox_words =
"bottom"
"close "
"down "
"left "
"page "
"right "
"top "
"up ";
enum mixer_word {
MW_ALL = (1U << 1),
MW_BALANCE = (1U << 2),
MW_CAPTURE = (1U << 3),
MW_CARD = (1U << 4),
MW_CLOSE = (1U << 5),
MW_CONTROL = (1U << 6),
MW_DOWN = (1U << 7),
MW_FOCUS = (1U << 8),
MW_HELP = (1U << 9),
MW_INFORMATION = (1U << 10),
MW_LEFT = (1U << 11),
MW_MODE = (1U << 12),
MW_MUTE = (1U << 13),
MW_NEXT = (1U << 14),
MW_PLAYBACK = (1U << 15),
MW_PREVIOUS = (1U << 16),
MW_REFRESH = (1U << 17),
MW_RIGHT = (1U << 18),
MW_SELECT = (1U << 19),
MW_SET = (1U << 20),
MW_SYSTEM = (1U << 21),
MW_TOGGLE = (1U << 22),
MW_UP = (1U << 23),
};
const char *mixer_words =
"all "
"balance "
"capture "
"card "
"close "
"control "
"down "
"focus "
"help "
"information"
"left "
"mode "
"mute "
"next "
"playback "
"previous "
"refresh "
"right "
"select "
"set "
"system "
"toggle "
"up ";
static unsigned int parse_words(const char *name, const char* wordlist, unsigned int itemlen, unsigned int *number) {
unsigned int words = 0;
unsigned int word;
int i;
char buf[16];
char *endptr;
while (*name) {
for (i = 0; i < (int)sizeof(buf) - 1; ++i) {
if (*name == '\0')
break;
if (*name == '_') {
++name;
break;
}
buf[i] = *name;
++name;
}
buf[i] = '\0';
if (buf[0] >= '0' && buf[0] <= '9') {
if (number) {
*number = strtoumax(buf, &endptr, 10);
if (*endptr != '\0')
return 0;
}
word = W_NUMBER;
}
else if ((i = strlist_index(wordlist, itemlen, buf)) >= 0)
word = i <= 30 ? (2U << i) : 0;
else
return 0;
if (words & word) // no duplicate words
return 0;
words |= word;
}
return words;
}
static int textbox_command_by_name(const char *name) {
switch (parse_words(name, textbox_words, 6, NULL)) {
case TW_TOP: return CMD_TEXTBOX_TOP;
case TW_BOTTOM: return CMD_TEXTBOX_BOTTOM;
case TW_CLOSE: return CMD_TEXTBOX_CLOSE;
case TW_UP: return CMD_TEXTBOX_UP;
case TW_DOWN: return CMD_TEXTBOX_DOWN;
case TW_LEFT: return CMD_TEXTBOX_LEFT;
case TW_RIGHT: return CMD_TEXTBOX_RIGHT;
case TW_PAGE|TW_UP: return CMD_TEXTBOX_PAGE_UP;
case TW_PAGE|TW_DOWN: return CMD_TEXTBOX_PAGE_DOWN;
case TW_PAGE|TW_LEFT: return CMD_TEXTBOX_PAGE_LEFT;
case TW_PAGE|TW_RIGHT: return CMD_TEXTBOX_PAGE_RIGHT;
default: return 0;
}
}
static int mixer_command_by_name(const char *name) {
unsigned int channel = 0;
unsigned int number = 1; // default numeric arg
unsigned int words = parse_words(name, mixer_words, 11, &number);
switch (words) {
case MW_HELP: return CMD_MIXER_HELP;
case MW_CLOSE: return CMD_MIXER_CLOSE;
case MW_REFRESH: return CMD_MIXER_REFRESH;
case MW_SELECT|MW_CARD: return CMD_MIXER_SELECT_CARD;
case MW_SYSTEM|MW_INFORMATION: return CMD_MIXER_SYSTEM_INFORMATION;
case MW_MODE|MW_ALL: return CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_ALL);
case MW_MODE|MW_CAPTURE: return CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_CAPTURE);
case MW_MODE|MW_PLAYBACK: return CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_PLAYBACK);
case MW_MODE|MW_TOGGLE: return CMD_MIXER_TOGGLE_VIEW_MODE;
case MW_CONTROL|MW_BALANCE: return CMD_MIXER_BALANCE_CONTROL;
case MW_NEXT:
case MW_NEXT|W_NUMBER:
case MW_PREVIOUS:
case MW_PREVIOUS|W_NUMBER:
return ((number < 1 || number > 511) ? 0 :
CMD_WITH_ARG((words & MW_NEXT
? CMD_MIXER_NEXT
: CMD_MIXER_PREVIOUS), number));
case MW_CONTROL|MW_FOCUS|W_NUMBER:
return ((number < 1 || number > 512) ? 0 :
CMD_WITH_ARG(CMD_MIXER_FOCUS_CONTROL, number - 1));
}
if (words & MW_LEFT)
channel |= LEFT;
if (words & MW_RIGHT)
channel |= RIGHT;
if (!channel)
channel = LEFT|RIGHT;
switch (words & ~(MW_LEFT|MW_RIGHT)) {
case MW_CONTROL|MW_UP:
case MW_CONTROL|MW_UP|W_NUMBER:
case MW_CONTROL|MW_DOWN:
case MW_CONTROL|MW_DOWN|W_NUMBER:
return ((number < 1 || number > 100) ? 0 :
CMD_WITH_ARG((words & MW_UP
? CMD_MIXER_CONTROL_UP_LEFT
: CMD_MIXER_CONTROL_DOWN_LEFT) + channel - 1, number));
case MW_CONTROL|MW_SET|W_NUMBER:
return (number > 100 ? 0 :
CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT_LEFT + channel - 1, number));
case MW_TOGGLE|MW_MUTE:
return CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, channel);
case MW_TOGGLE|MW_CAPTURE:
return CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, channel);
}
return 0;
}
static int* element_by_name(const char *name) {
int idx = strlist_index(
#ifdef TRICOLOR_VOLUME_BAR
"ctl_bar_hi "
#endif
"ctl_bar_lo "
#ifdef TRICOLOR_VOLUME_BAR
"ctl_bar_mi "
#endif
"ctl_capture "
"ctl_frame "
"ctl_inactive "
"ctl_label "
"ctl_label_focus "
"ctl_label_inactive"
"ctl_mark_focus "
"ctl_mute "
"ctl_nocapture "
"ctl_nomute "
"errormsg "
"infomsg "
"menu "
"menu_selected "
"mixer_active "
"mixer_frame "
"mixer_text "
"textbox "
"textfield ", 18, name);
if (idx < 0) {
#ifndef TRICOLOR_VOLUME_BAR
if (strlist_index(
"ctl_bar_hi"
"ctl_bar_mi", 10, name) >= 0)
return &errno; // dummy element
#endif
return NULL;
}
return &( ((int*) &attrs)[idx] );
}
static int cfg_bind(char **argv, unsigned int argc) {
const char *command_name;
command_enum command = 0;
unsigned int i;
int keys[3] = { -1, -1, -1 };
union {
command_enum *mixer_bindings;
uint8_t *textbox_bindings;
} bind_to = {
.mixer_bindings = mixer_bindings
};
if (argc == 2)
command_name = argv[1];
else if (argc == 3) {
command_name = argv[2];
if (! strcmp(argv[1], "textbox")) {
bind_to.textbox_bindings = textbox_bindings;
}
else if (! strcmp(argv[1], "mixer"))
; // bind_to.mixer_bindings = mixer_bindings
else {
error_message = _("invalid widget");
error_cause = argv[1];
return ERROR_CONFIG;
}
}
else {
return (argc < 2 ? ERROR_MISSING_ARGUMENTS : ERROR_TOO_MUCH_ARGUMENTS);
}
keys[0] = curskey_parse(argv[0]);
if (keys[0] < 0 || keys[0] >= (int)ARRAY_SIZE(mixer_bindings)) {
error_message = _("invalid key");
error_cause = argv[0];
return ERROR_CONFIG;
}
if (keys[0] == KEY_ENTER || keys[0] == '\n' || keys[0] == '\r') {
keys[0] = KEY_ENTER;
keys[1] = '\n';
keys[2] = '\r';
}
if (bind_to.textbox_bindings == textbox_bindings)
command = textbox_command_by_name(command_name);
else
command = mixer_command_by_name(command_name);
if (!command) {
if (!strcmp(command_name, "none"))
; // command = 0
else {
error_message = _("invalid command");
error_cause = command_name;
return ERROR_CONFIG;
}
}
for (i = 0; i < ARRAY_SIZE(keys) && keys[i] != -1; ++i) {
if (bind_to.textbox_bindings == textbox_bindings)
bind_to.textbox_bindings[keys[i]] = command;
else
bind_to.mixer_bindings[keys[i]] = command;
}
return 0;
}
static int cfg_color(char **argv, unsigned int argc)
{
short fg_color, bg_color;
unsigned int i;
int *element;
int attr;
if (argc < 3)
return ERROR_MISSING_ARGUMENTS;
if (NULL == (element = element_by_name(argv[0]))) {
error_message = _("unknown theme element");
error_cause = argv[0];
return ERROR_CONFIG;
}
if (-2 == (fg_color = color_by_name(argv[1]))) {
error_message = _("unknown color");
error_cause = argv[1];
return ERROR_CONFIG;
}
if (-2 == (bg_color = color_by_name(argv[2]))) {
error_message = _("unknown color");
error_cause = argv[2];
return ERROR_CONFIG;
}
*element = get_color_pair(fg_color, bg_color);
for (i = 3; i < argc; ++i) {
if (-1 == (attr = attr_by_name(argv[i]))) {
error_message = _("unknown color attribute");
error_cause = argv[i];
return ERROR_CONFIG;
}
else
*element |= attr;
}
return 0;
}
static int cfg_set(char **argv, unsigned int argc)
{
char *endptr;
if (argc == 2) {
if (! strcmp(argv[0], "mouse_wheel_step")) {
mouse_wheel_step = strtoumax(argv[1], &endptr, 10);
if (mouse_wheel_step > 100 || *endptr != '\0') {
mouse_wheel_step = 1;
error_message = _("invalid value");
error_cause = argv[1];
return ERROR_CONFIG;
}
}
else if (! strcmp(argv[0], "mouse_wheel_focuses_control")) {
if ((argv[1][0] == '0' || argv[1][0] == '1') && argv[1][1] == '\0')
mouse_wheel_focuses_control = argv[1][0] - '0';
else {
error_message = _("invalid value");
error_cause = argv[1];
return ERROR_CONFIG;
}
}
else if (!strcmp(argv[0], "background")) {
int bg_color = color_by_name(argv[1]);
if (bg_color == -2) {
error_message = _("unknown color");
error_cause = argv[1];
return ERROR_CONFIG;
}
reinit_colors(bg_color);
}
else {
error_message = _("unknown option");
error_cause = argv[0];
return ERROR_CONFIG;
}
}
else {
return (argc < 2 ? ERROR_MISSING_ARGUMENTS : ERROR_TOO_MUCH_ARGUMENTS);
}
return 0;
}
/* Split $line on whitespace, store it in $args, return the argument count.
* Return 0 for commented lines ('\s*#').
*
* This will modify contents of $line.
*/
static unsigned int parse_line(char *line, char **args, unsigned int args_size)
{
unsigned int count;
for (count = 0; count < args_size; ++count) {
while (*line && isspace(*line))
++line;
if (*line == '\0')
break;
if (*line == '#' && count == 0)
break;
args[count] = line;
while (*line && !isspace(*line))
++line;
if (*line != '\0') {
*line = '\0';
++line;
}
}
return count;
}
static int process_line(char *line) {
char *args[16];
unsigned int argc = parse_line(line, args, ARRAY_SIZE(args));
int ret = 0;
if (argc >= 1) {
error_cause = NULL;
//error_message = _("unknown error");
if (argc >= ARRAY_SIZE(args))
ret = ERROR_TOO_MUCH_ARGUMENTS;
else {
ret = strlist_index(
"bind "
"color"
"set ", 5, args[0]);
switch (ret) {
case 0: ret = cfg_bind(args + 1, argc - 1); break;
case 1: ret = cfg_color(args + 1, argc - 1); break;
case 2: ret = cfg_set(args + 1, argc - 1); break;
default: error_message = _("unknown command");
}
}
if (ret == ERROR_MISSING_ARGUMENTS)
error_message = _("missing arguments");
else if (ret == ERROR_TOO_MUCH_ARGUMENTS)
error_message = _("too much arguments");
}
return ret;
}
void parse_config_file(const char *file_name)
{
char *buf;
unsigned int file_size;
unsigned int lineno;
unsigned int i;
char *line;
endwin(); // print warnings to stderr
buf = read_file(file_name, &file_size);
if (!buf) {
fprintf(stderr, "%s: %s\n", file_name, strerror(errno));
return;
}
curskey_init();
curskey_define_meta_keys(128);
lineno = 0;
line = buf;
for (i = 0; i < file_size; ++i) {
if (buf[i] == '\n') {
buf[i] = '\0';
++lineno;
if (process_line(line) < 0) {
if (error_cause)
fprintf(stderr, "%s:%d: %s: %s: %s\n", file_name, lineno, line, error_message, error_cause);
else
fprintf(stderr, "%s:%d: %s: %s\n", file_name, lineno, line, error_message);
}
line = &buf[i + 1];
}
}
free(buf);
curskey_destroy();
}
void parse_default_config_file() {
char file[4096];
const char *home;
home = getenv("XDG_CONFIG_HOME");
if (home && *home) {
snprintf(file, sizeof(file), "%s/alsamixer.rc", home);
if (! access(file, F_OK))
return parse_config_file(file);
}
home = getenv("HOME");
if (!home || !*home) {
struct passwd *pwd = getpwuid(getuid());
if (pwd)
home = pwd->pw_dir;
}
if (home && *home) {
snprintf(file, sizeof(file), "%s/.config/alsamixer.rc", home);
if (! access(file, F_OK))
return parse_config_file(file);
snprintf(file, sizeof(file), "%s/.alsamixer.rc", home);
if (! access(file, F_OK))
return parse_config_file(file);
}
}

9
alsamixer/configparser.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef CONFIGPARSER_H_INCLUDED
#define CONFIGPARSER_H_INCLUDED
#define CONFIG_DEFAULT ((const char*) 1)
void parse_config_file(const char *file);
void parse_default_config_file();
#endif

274
alsamixer/curskey.c Normal file
View file

@ -0,0 +1,274 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "curskey.h"
#include "utils.h"
#include "mem.h"
struct curskey_key {
char *keyname;
int keycode;
};
static struct curskey_key *keynames;
static unsigned int keynames_count;
unsigned int meta_keycode_start;
static uint64_t invalid_meta_char_mask[2];
// Names for non-printable/whitespace characters and aliases for existing keys
static const struct curskey_key keyname_aliases[] = {
// Sorted by `keyname`
{ "DEL", KEY_DEL },
{ "DELETE", KEY_DC },
{ "ENTER", '\n' },
{ "ENTER", '\r' },
{ "ESCAPE", KEY_ESCAPE },
{ "INSERT", KEY_IC },
{ "PAGEDOWN", KEY_NPAGE },
{ "PAGEUP", KEY_PPAGE },
{ "SPACE", KEY_SPACE },
{ "TAB", KEY_TAB }
};
#define STARTSWITH_KEY(S) \
((name[0] == 'K' || name[0] == 'k') && \
(name[1] == 'E' || name[1] == 'e') && \
(name[2] == 'Y' || name[2] == 'y') && \
(name[3] == '_'))
#define IS_META(S) \
((S[0] == 'M' || S[0] == 'm' || S[0] == 'A' || S[0] == 'a') && S[1] == '-')
static int curskey_key_cmp(const void *a, const void *b) {
return strcmp(((struct curskey_key*) a)->keyname,
((struct curskey_key*) b)->keyname);
}
static int curskey_find(const struct curskey_key *table, unsigned int size, const char *name) {
unsigned int start = 0;
unsigned int end = size;
unsigned int i;
int cmp;
while (1) {
i = (start+end) / 2;
cmp = strcasecmp(name, table[i].keyname);
if (cmp == 0)
return table[i].keycode;
else if (end == start + 1)
return ERR;
else if (cmp > 0)
start = i;
else
end = i;
}
}
/* Translate the name of a ncurses KEY_ constant to its value.
* "KEY_DOWN" -> 258
*
* Return ERR on failure.
*/
int curskey_keycode(const char *name)
{
int i;
if (! name)
return ERR;
if (STARTSWITH_KEY(name))
name += 4;
if (name[0] == 'F' || name[0] == 'f') {
i = (name[1] == '(' ? 2 : 1);
if (name[i] >= '0' && name[i] <= '9') {
i = atoi(name + i);
if (i >= 1 && i <= 63)
return KEY_F(i);
}
}
i = curskey_find(keyname_aliases, ARRAY_SIZE(keyname_aliases), name);
if (i != ERR)
return i;
return curskey_find(keynames, keynames_count, name);
}
static void free_ncurses_keynames() {
if (keynames) {
while (keynames_count)
free(keynames[--keynames_count].keyname);
free(keynames);
keynames = NULL;
}
}
/* Create the list of ncurses KEY_ constants and their names.
* Returns OK on success, ERR on failure.
*/
int create_ncurses_keynames() {
int key;
char *name;
free_ncurses_keynames();
keynames = ccalloc(sizeof(struct curskey_key), (KEY_MAX - KEY_MIN));
for (key = KEY_MIN; key != KEY_MAX; ++key) {
name = (char*) keyname(key);
if (!name || !STARTSWITH_KEY(name))
continue;
name += 4;
if (name[0] == 'F' && name[1] == '(')
continue; // ignore KEY_F(1),...
keynames[keynames_count].keycode = key;
keynames[keynames_count].keyname = cstrdup(name);
++keynames_count;
}
keynames = crealloc(keynames, keynames_count * sizeof(struct curskey_key));
qsort(keynames, keynames_count, sizeof(struct curskey_key), curskey_key_cmp);
return OK;
}
/* Defines meta escape sequences in ncurses.
*
* Some combinations with meta/alt may not be available since they collide
* with the prefix of a pre-defined key.
* For example, keys F1 - F4 begin with "\eO", so ALT-O cannot be defined.
*
* Returns OK if meta keys are available, ERR otherwise.
*/
int curskey_define_meta_keys(unsigned int keycode_start) {
#ifdef NCURSES_VERSION
int ch;
int keycode;
int new_keycode = keycode_start;
char key_sequence[3] = "\e ";
invalid_meta_char_mask[0] = 0;
invalid_meta_char_mask[1] = 0;
for (ch = 0; ch <= CURSKEY_MAX_META_CHAR; ++ch) {
key_sequence[1] = ch;
keycode = key_defined(key_sequence);
if (! keycode) {
define_key(key_sequence, new_keycode);
}
else if (keycode == new_keycode)
;
else
invalid_meta_char_mask[ch/65] |= (1UL << (ch % 64));
++new_keycode;
}
meta_keycode_start = keycode_start;
return OK;
#endif
return ERR;
}
/* Return the keycode for a key with modifiers applied.
*
* Available modifiers are:
* - CURSKEY_MOD_META / CURSKEY_MOD_ALT
* - CURSKEY_MOD_CNTRL
*
* See also the macros curskey_meta_key(), curskey_cntrl_key().
*
* Returns ERR if the modifiers cannot be applied to this key.
*/
int curskey_mod_key(int key, unsigned int modifiers) {
if (modifiers & CURSKEY_MOD_CNTRL) {
if ((key >= 'A' && key <= '_') || (key >= 'a' && key <= 'z') || key == ' ')
key = key % 32;
else
return ERR;
}
if (modifiers & CURSKEY_MOD_META) {
if (meta_keycode_start &&
(key >= 0 && key <= CURSKEY_MAX_META_CHAR) &&
! (invalid_meta_char_mask[key/65] & (1UL << (key % 64)))) {
key = meta_keycode_start + key;
}
else
return ERR;
}
return key;
}
/* Return the ncurses keycode for a key definition.
*
* Key definition may be:
* - Single character (a, z, ...)
* - Character with control-modifier (^x, C-x, c-x, ...)
* - Character with meta/alt-modifier (M-x, m-x, A-x, a-x, ...)
* - Character with both modifiers (C-M-x, M-C-x, M-^x, ...)
* - Curses keyname, no modifiers allowed (KEY_HOME, HOME, F1, F(1), ...)
*
* Returns ERR if either
* - The key definition is NULL or empty
* - The key could not be found ("KEY_FOO")
* - The key combination is invalid in general ("C-TAB", "C-RETURN")
* - The key is invalid because of compile time options (the
* `define_key()` function was not available.)
* - The key is invalid because it could not be defined by
* curskey_define_meta_keys()
*/
int curskey_parse(const char *def) {
int c;
unsigned int mod = 0;
if (! def)
return ERR;
for (;;) {
if (def[0] == '^' && def[1] != '\0') {
++def;
mod |= CURSKEY_MOD_CNTRL;
}
else if ((def[0] == 'C' || def[0] == 'c') && def[1] == '-') {
def += 2;
mod |= CURSKEY_MOD_CNTRL;
}
else if (IS_META(def)) {
if (! meta_keycode_start)
return ERR;
def += 2;
mod |= CURSKEY_MOD_ALT;
}
else
break;
}
if (*def == '\0')
return ERR;
else if (*(def+1) == '\0')
c = *def;
else
c = curskey_keycode(def);
return curskey_mod_key(c, mod);
}
/* Initialize curskey.
* Returns OK on success, ERR on failure. */
int curskey_init() {
keypad(stdscr, TRUE);
return create_ncurses_keynames();
}
/* Destroy curskey. */
void curskey_destroy() {
free_ncurses_keynames();
}

37
alsamixer/curskey.h Normal file
View file

@ -0,0 +1,37 @@
#ifndef CURSKEY_H_INCLUDED
#define CURSKEY_H_INCLUDED
#include CURSESINC
/* Additional KEY_ constants */
#define KEY_SPACE ' '
#define KEY_TAB '\t'
#define KEY_DEL 127
#define KEY_ESCAPE 27
#define KEY_INSERT KEY_IC
#define KEY_DELETE KEY_DC
#define KEY_PAGEUP KEY_PPAGE
#define KEY_PAGEDOWN KEY_NPAGE
/* Modifiers */
#define CURSKEY_MOD_CNTRL 1U
#define CURSKEY_MOD_META 2U
#define CURSKEY_MOD_ALT CURSKEY_MOD_META
/* Defines the range of characters which should be "meta-able" */
#define CURSKEY_MAX_META_CHAR 127
int curskey_init();
void curskey_destroy();
int curskey_define_meta_keys(unsigned int keycode_start);
int curskey_parse(const char *keydef);
int curskey_mod_key(int key, unsigned int modifiers);
#define curskey_meta_key(KEY) \
curskey_mod_key(KEY, CURSKEY_MOD_META)
#define curskey_cntrl_key(KEY) \
curskey_mod_key(KEY, CURSKEY_MOD_CNTRL)
#endif

195
alsamixer/device_name.c Normal file
View file

@ -0,0 +1,195 @@
/*
* device_name_form.c - ask for sound control device name
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdlib.h>
#include <string.h>
#include CURSESINC
#include <form.h>
#include "gettext_curses.h"
#include "die.h"
#include "mem.h"
#include "utils.h"
#include "colors.h"
#include "widget.h"
#include "mixer_widget.h"
#include "card_select.h"
#include "device_name.h"
static struct widget form_widget;
static FIELD *fields[3];
static FORM *form;
static char *dup_current_name(void)
{
int rows, cols, max, i;
char *s;
if (form_driver(form, REQ_VALIDATION) == E_OK) {
dynamic_field_info(fields[1], &rows, &cols, &max);
s = ccalloc(1, cols + 1);
memcpy(s, field_buffer(fields[1], 0), cols);
for (i = strlen(s) - 1; i >= 0 && s[i] == ' '; --i)
s[i] = '\0';
return s;
} else {
return cstrdup("");
}
}
static void on_key_enter(void)
{
char *s;
bool ok;
s = dup_current_name();
ok = select_card_by_name(s);
free(s);
if (ok) {
form_widget.close();
close_card_select_list();
}
}
static void on_form_key(int key)
{
static const struct {
int key;
int request;
} key_map[] = {
{ KEY_LEFT, REQ_PREV_CHAR },
{ KEY_RIGHT, REQ_NEXT_CHAR },
{ KEY_HOME, REQ_BEG_FIELD },
{ KEY_BACKSPACE, REQ_DEL_PREV },
{ 127, REQ_DEL_PREV },
{ KEY_DC, REQ_DEL_CHAR },
{ KEY_BEG, REQ_BEG_FIELD },
{ KEY_END, REQ_END_FIELD },
};
unsigned int i;
if (key >= 32 && key < 256 && key != 127) {
form_driver(form, key);
return;
}
for (i = 0; i < ARRAY_SIZE(key_map); ++i)
if (key_map[i].key == key) {
form_driver(form, key_map[i].request);
break;
}
}
static void on_handle_key(int key)
{
switch (key) {
case 27:
case KEY_CANCEL:
form_widget.close();
break;
case 10:
case 13:
case KEY_ENTER:
on_key_enter();
break;
default:
on_form_key(key);
break;
}
}
static bool create(void)
{
const char *title;
if (screen_lines < 6 || screen_cols < 36) {
form_widget.close();
beep();
return FALSE;
}
widget_init(&form_widget,
6, 36, SCREEN_CENTER, SCREEN_CENTER,
attrs.textbox, WIDGET_BORDER | WIDGET_SUBWINDOW | WIDGET_CURSOR_VISIBLE);
title = _("Sound Card");
mvwprintw(form_widget.window, 0, (36 - 2 - get_mbs_width(title)) / 2, " %s ", title);
set_form_win(form, form_widget.window);
set_form_sub(form, form_widget.subwindow);
return TRUE;
}
static void on_window_size_changed(void)
{
form_driver(form, REQ_VALIDATION); /* save field value */
unpost_form(form);
if (!create())
return;
/*
* This call fails because ncurses does not allow changing options of
* the current field, and we cannot change the current field because
* there is only one. The only way to make this work would be to throw
* away and recreate all fields.
*/
field_opts_off(fields[1], O_BLANK);
post_form(form);
}
static void on_close(void)
{
unpost_form(form);
free_form(form);
free_field(fields[0]);
free_field(fields[1]);
widget_free(&form_widget);
}
static struct widget form_widget = {
.handle_key = on_handle_key,
.window_size_changed = on_window_size_changed,
.close = on_close,
};
void create_device_name_form(void)
{
fields[0] = new_field(1, 32, 1, 1, 0, 0);
if (!fields[0])
fatal_error("cannot create field");
field_opts_off(fields[0], O_ACTIVE|O_EDIT);
set_field_fore(fields[0], attrs.textbox);
set_field_back(fields[0], attrs.textbox);
set_field_buffer(fields[0], 0, _("Device name:"));
fields[1] = new_field(1, 32, 2, 1, 0, 0);
if (!fields[1])
fatal_error("cannot create field");
field_opts_off(fields[1], O_AUTOSKIP|O_NULLOK|O_STATIC);
set_field_fore(fields[1], attrs.textfield);
set_field_back(fields[1], attrs.textfield);
set_field_buffer(fields[1], 0, mixer_device_name);
form = new_form(fields);
if (!form)
fatal_error("cannot create form");
if (!create())
return;
post_form(form);
}

6
alsamixer/device_name.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef DEVICE_NAME_FORM_H_INCLUDED
#define DEVICE_NAME_FORM_H_INCLUDED
void create_device_name_form(void);
#endif

39
alsamixer/die.c Normal file
View file

@ -0,0 +1,39 @@
/*
* die.c - error handlers
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include "gettext_curses.h"
#include "mainloop.h"
#include "die.h"
void fatal_error(const char *msg)
{
app_shutdown();
fprintf(stderr, "%s\n", msg);
exit(EXIT_FAILURE);
}
void fatal_alsa_error(const char *msg, int err)
{
app_shutdown();
fprintf(stderr, _("%s: %s\n"), msg, snd_strerror(err));
exit(EXIT_FAILURE);
}

7
alsamixer/die.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef DIE_H_INCLUDED
#define DIE_H_INCLUDED
void fatal_error(const char *msg) __attribute__((__noreturn__));
void fatal_alsa_error(const char *msg, int err) __attribute__((__noreturn__));
#endif

10
alsamixer/go Executable file
View file

@ -0,0 +1,10 @@
#!/bin/bash
#GDB="gdb --args"
GDB=""
ALSA_MIXER_SIMPLE_MODULES="$HOME/hg/alsa-lib/modules/mixer/simple/.libs" \
ALSA_MIXER_SIMPLE="$HOME/hg/alsa-lib/src/conf/smixer.conf" \
ALSA_MIXER_SIMPLE_MPYTHON="$HOME/hg/alsa-lib/modules/mixer/simple/python/main.py" \
LD_PRELOAD="$HOME/hg/alsa-lib/src/.libs/libasound.so" \
$GDB ./alsamixer "$@"

148
alsamixer/mainloop.c Normal file
View file

@ -0,0 +1,148 @@
/*
* mainloop.c - main loop
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <poll.h>
#include <panel.h>
#include <alsa/asoundlib.h>
#include "mem.h"
#include "die.h"
#include "colors.h"
#include "widget.h"
#include "mixer_widget.h"
#include "mixer_display.h"
#include "mixer_controls.h"
#include "mainloop.h"
static WINDOW *curses_initialized;
static void black_hole_error_handler(const char *file ATTRIBUTE_UNUSED,
int line ATTRIBUTE_UNUSED,
const char *function ATTRIBUTE_UNUSED,
int err ATTRIBUTE_UNUSED,
const char *fmt ATTRIBUTE_UNUSED, ...)
{
}
void initialize_curses(bool use_color, bool use_mouse)
{
curses_initialized = initscr();
cbreak();
noecho();
#ifdef HAVE_CURSES_ESCDELAY
set_escdelay(100);
#endif
window_size_changed(); /* update screen_lines/cols */
init_colors(use_color);
if (use_mouse)
mousemask(ALL_MOUSE_EVENTS, NULL);
snd_lib_error_set_handler(black_hole_error_handler);
}
void app_shutdown(void)
{
if (curses_initialized) {
clear();
refresh();
curs_set(1);
endwin();
}
mixer_shutdown();
}
void mainloop(void)
{
struct pollfd *pollfds = NULL;
int nfds = 0, n;
const struct widget *active_widget;
unsigned short revents;
int key;
int err;
for (;;) {
update_panels();
doupdate();
active_widget = get_active_widget();
if (!active_widget)
break;
n = 1 + snd_mixer_poll_descriptors_count(mixer);
if (n != nfds) {
free(pollfds);
nfds = n;
pollfds = ccalloc(nfds, sizeof *pollfds);
pollfds[0].fd = fileno(stdin);
pollfds[0].events = POLLIN;
}
err = snd_mixer_poll_descriptors(mixer, &pollfds[1], nfds - 1);
if (err < 0)
fatal_alsa_error("cannot get poll descriptors", err);
n = poll(pollfds, nfds, -1);
if (n < 0) {
if (errno == EINTR) {
pollfds[0].revents = 0;
doupdate(); /* handle SIGWINCH */
} else {
fatal_error("poll error");
}
}
if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
break;
if (pollfds[0].revents & POLLIN)
--n;
if (n > 0) {
err = snd_mixer_poll_descriptors_revents(mixer, &pollfds[1], nfds - 1, &revents);
if (err < 0)
fatal_alsa_error("cannot get poll events", err);
if (revents & (POLLERR | POLLNVAL))
close_mixer_device();
else if (revents & POLLIN)
snd_mixer_handle_events(mixer);
}
key = wgetch(active_widget->window);
while (key != ERR) {
#ifdef KEY_RESIZE
if (key == KEY_RESIZE)
window_size_changed();
else
#endif
active_widget->handle_key(key);
active_widget = get_active_widget();
if (!active_widget)
break;
key = wgetch(active_widget->window);
}
if (!active_widget)
break;
if (controls_changed) {
controls_changed = FALSE;
create_controls();
control_values_changed = FALSE;
display_controls();
} else if (control_values_changed) {
control_values_changed = FALSE;
display_controls();
}
}
free(pollfds);
}

10
alsamixer/mainloop.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef MAINLOOP_H_INCLUDED
#define MAINLOOP_H_INCLUDED
#include CURSESINC
void initialize_curses(bool use_color, bool use_mouse);
void mainloop(void);
void app_shutdown(void);
#endif

67
alsamixer/mem.c Normal file
View file

@ -0,0 +1,67 @@
/*
* mem.c - memory allocation checkers
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include "die.h"
#include "mem.h"
static void check(void *p)
{
if (!p)
fatal_error("out of memory");
}
void *ccalloc(size_t n, size_t size)
{
void *mem = calloc(n, size);
if (n && size)
check(mem);
return mem;
}
void *crealloc(void *ptr, size_t new_size)
{
ptr = realloc(ptr, new_size);
if (new_size)
check(ptr);
return ptr;
}
char *cstrdup(const char *s)
{
char *str = strdup(s);
check(str);
return str;
}
char *casprintf(const char *fmt, ...)
{
va_list ap;
char *str;
va_start(ap, fmt);
if (vasprintf(&str, fmt, ap) < 0)
check(NULL);
va_end(ap);
return str;
}

11
alsamixer/mem.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef MEM_H_INCLUDED
#define MEM_H_INCLUDED
#include <stddef.h>
void *ccalloc(size_t n, size_t size);
void *crealloc(void *ptr, size_t new_size);
char *cstrdup(const char *s);
char *casprintf(const char *fmt, ...) __attribute__((__format__(printf, 1, 2)));
#endif

92
alsamixer/menu_widget.c Normal file
View file

@ -0,0 +1,92 @@
#include "menu_widget.h"
#include "colors.h"
#include "utils.h"
#include "bindings.h"
/* Returns
* - KEY_CANCEL: close is requested
* - KEY_ENTER: item is selected
* - -1: no action
*/
int menu_widget_handle_key(MENU *menu, int key)
{
MEVENT m;
switch (key) {
case 27:
case KEY_CANCEL:
case 'q':
case 'Q':
return KEY_CANCEL;
case '\n':
case '\r':
case KEY_ENTER:
return KEY_ENTER;
case KEY_MOUSE:
switch (menu_driver(menu, KEY_MOUSE)) {
case E_UNKNOWN_COMMAND:
/* If you double-click an item a REQ_TOGGLE_ITEM is generated
* and E_UNKNOWN_COMMAND is returned. (man menu_driver) */
return KEY_ENTER;
case E_REQUEST_DENIED:
/* If menu did not handle KEY_MOUSE is has to be removed from
* input queue to prevent an infinite loop. */
key = wgetch(menu_win(menu));
if (key == KEY_MOUSE) {
if (getmouse(&m) == ERR)
return -1;
if (m.bstate & (BUTTON4_PRESSED|BUTTON4_CLICKED))
menu_driver(menu, REQ_UP_ITEM);
#if NCURSES_MOUSE_VERSION > 1
else if (m.bstate & (BUTTON5_PRESSED|BUTTON5_CLICKED))
menu_driver(menu, REQ_DOWN_ITEM);
#endif
else
return KEY_CANCEL;
}
else if (key > 0)
ungetch(key);
}
return -1;
default:
if (key < (int)ARRAY_SIZE(textbox_bindings)) {
key = textbox_bindings[key];
if (key >= CMD_TEXTBOX___MIN_MENU_COMMAND &&
key <= CMD_TEXTBOX___MAX_MENU_COMMAND)
menu_driver(menu, key + KEY_MAX);
}
return -1;
}
}
void menu_widget_create(struct widget *widget, MENU *menu, const char *title)
{
int rows, columns;
if (menu)
unpost_menu(menu);
if (screen_lines < 3 || screen_cols < 10) {
widget->close();
beep();
return;
}
scale_menu(menu, &rows, &columns);
rows += 2;
columns += 2;
if (rows > screen_lines)
rows = screen_lines;
if (columns > screen_cols)
columns = screen_cols;
widget_init(widget, rows, columns, SCREEN_CENTER, SCREEN_CENTER,
attrs.menu, WIDGET_BORDER | WIDGET_SUBWINDOW);
mvwprintw(widget->window, 0, (columns - 2 - get_mbs_width(title)) / 2, " %s ", title);
set_menu_win(menu, widget->window);
set_menu_sub(menu, widget->subwindow);
post_menu(menu);
}

10
alsamixer/menu_widget.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef MENU_WIDGET_H_INCLUDED
#define MENU_WIDGET_H_INCLUDED
#include "widget.h"
#include <menu.h>
int menu_widget_handle_key(MENU *menu, int key);
void menu_widget_create(struct widget *widget, MENU *menu, const char *title);
#endif

136
alsamixer/mixer_clickable.c Normal file
View file

@ -0,0 +1,136 @@
#include <stdlib.h>
#include <string.h>
#include "mixer_clickable.h"
extern int screen_cols;
extern int screen_lines;
static struct clickable_rect *clickable_rects = NULL;
static unsigned int clickable_rects_count = 0;
static unsigned int last_rect = 0;
/* Using 0 instead of -1 for marking free rectangles allows us to use
* memset for `freeing` all rectangles at once.
* Zero is actually a valid coordinate in ncurses, but since we don't have
* any clickables in the top line this is fine. */
#define FREE_MARKER 0
#define RECT_IS_FREE(RECT) ((RECT).y1 == FREE_MARKER)
#define RECT_FREE(RECT) ((RECT).y1 = FREE_MARKER)
void clickable_set(int y1, int x1, int y2, int x2, command_enum command, int arg1) {
struct clickable_rect* tmp;
unsigned int i;
for (i = last_rect; i < clickable_rects_count; ++i) {
if (RECT_IS_FREE(clickable_rects[i])) {
last_rect = i;
goto SET_CLICKABLE_DATA;
}
}
for (i = 0; i < last_rect; ++i) {
if (RECT_IS_FREE(clickable_rects[i])) {
last_rect = i;
goto SET_CLICKABLE_DATA;
}
}
last_rect = clickable_rects_count;
tmp = realloc(clickable_rects, (clickable_rects_count + 8) * sizeof(*clickable_rects));
if (!tmp) {
free(clickable_rects);
clickable_rects = NULL;
clickable_rects_count = 0;
last_rect = 0;
return;
}
clickable_rects = tmp;
#if FREE_MARKER == 0
memset(clickable_rects + clickable_rects_count, 0, 8 * sizeof(*clickable_rects));
#else
for (i = clickable_rects_count; i < clickable_rects_count + 8; ++i)
RECT_FREE(clickable_rects[i]);
#endif
clickable_rects_count += 8;
SET_CLICKABLE_DATA:
clickable_rects[last_rect] = (struct clickable_rect) {
.y1 = y1,
.x1 = x1,
.x2 = x2,
.y2 = y2,
.command = command,
.arg1 = arg1
};
}
void clickable_set_relative(WINDOW *win, int y1, int x1, int y2, int x2, command_enum command, int arg1) {
int y, x;
getyx(win, y, x);
y1 = y + y1;
x1 = x + x1;
y2 = y + y2;
x2 = x + x2;
clickable_set(y1, x1, y2, x2, command, arg1);
}
void clickable_clear(int y1, int x1, int y2, int x2) {
#define IS_IN_RECT(Y, X) (Y >= y1 && Y <= y2 && X >= x1 && X <= x2)
unsigned int i;
if (x1 == 0 && x2 == -1 && y2 == -1) {
if (y1 == 0) {
// Optimize case: clear all
#if FREE_MARKER == 0
if (clickable_rects)
memset(clickable_rects, 0,
clickable_rects_count * sizeof(*clickable_rects));
#else
for (i = 0; i < clickable_rects_count; ++i)
RECT_FREE(clickable_rects[i]);
#endif
}
else {
// Optimize case: clear all lines beyond y1
for (i = 0; i < clickable_rects_count; ++i) {
if (clickable_rects[i].y2 >= y1)
RECT_FREE(clickable_rects[i]);
}
}
return;
}
if (y2 < 0)
y2 = screen_lines + y2 + 1;
if (x2 < 0)
x2 = screen_cols + x2 + 1;
for (i = 0; i < clickable_rects_count; ++i) {
if (!RECT_IS_FREE(clickable_rects[i]) && (
IS_IN_RECT(clickable_rects[i].y1, clickable_rects[i].x1) ||
IS_IN_RECT(clickable_rects[i].y2, clickable_rects[i].x2)
))
{
RECT_FREE(clickable_rects[i]);
}
}
}
struct clickable_rect* clickable_find(int y, int x) {
unsigned int i;
for (i = 0; i < clickable_rects_count; ++i) {
if (
!RECT_IS_FREE(clickable_rects[i]) &&
y >= clickable_rects[i].y1 &&
x >= clickable_rects[i].x1 &&
y <= clickable_rects[i].y2 &&
x <= clickable_rects[i].x2
)
{
return &clickable_rects[i];
}
}
return NULL;
}

View file

@ -0,0 +1,21 @@
#ifndef MIXER_CLICKABLE_H
#define MIXER_CLICKABLE_H
#include CURSESINC
#include "bindings.h"
struct clickable_rect {
short y1;
short x1;
short y2;
short x2;
command_enum command;
int arg1;
};
void clickable_set(int y1, int x1, int y2, int x2, command_enum command, int arg1);
void clickable_set_relative(WINDOW *win, int y1, int x1, int y2, int x2, command_enum command, int arg1);
void clickable_clear(int y1, int x1, int y2, int x2);
struct clickable_rect* clickable_find(int y, int x);
#endif

519
alsamixer/mixer_controls.c Normal file
View file

@ -0,0 +1,519 @@
/*
* mixer_controls.c - handles mixer controls and mapping from selems
* Copyright (c) 1998,1999 Tim Janik
* Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include CURSESINC
#include <alsa/asoundlib.h>
#include "utils.h"
#include "mem.h"
#include "mixer_display.h"
#include "mixer_widget.h"
#include "mixer_controls.h"
struct control *controls;
unsigned int controls_count;
static const snd_mixer_selem_channel_id_t supported_channels[] = {
SND_MIXER_SCHN_FRONT_LEFT,
SND_MIXER_SCHN_FRONT_RIGHT,
SND_MIXER_SCHN_REAR_LEFT,
SND_MIXER_SCHN_REAR_RIGHT,
SND_MIXER_SCHN_FRONT_CENTER,
SND_MIXER_SCHN_WOOFER,
SND_MIXER_SCHN_SIDE_LEFT,
SND_MIXER_SCHN_SIDE_RIGHT,
};
#define LAST_SUPPORTED_CHANNEL SND_MIXER_SCHN_SIDE_RIGHT
static const snd_mixer_selem_channel_id_t control_channels[][2] = {
{ SND_MIXER_SCHN_FRONT_LEFT, SND_MIXER_SCHN_FRONT_RIGHT },
{ SND_MIXER_SCHN_REAR_LEFT, SND_MIXER_SCHN_REAR_RIGHT },
{ SND_MIXER_SCHN_FRONT_CENTER, SND_MIXER_SCHN_UNKNOWN },
{ SND_MIXER_SCHN_WOOFER, SND_MIXER_SCHN_UNKNOWN },
{ SND_MIXER_SCHN_SIDE_LEFT, SND_MIXER_SCHN_SIDE_RIGHT },
};
bool are_there_any_controls(void)
{
snd_mixer_elem_t *elem;
unsigned int i;
for (elem = snd_mixer_first_elem(mixer);
elem;
elem = snd_mixer_elem_next(elem)) {
if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
continue;
if (snd_mixer_selem_is_enumerated(elem))
return TRUE;
if (snd_mixer_selem_has_playback_volume_joined(elem) ||
snd_mixer_selem_has_capture_volume_joined(elem) ||
snd_mixer_selem_has_playback_switch_joined(elem) ||
snd_mixer_selem_has_capture_switch_joined(elem))
return TRUE;
for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
if (snd_mixer_selem_has_playback_channel(elem, supported_channels[i]) ||
snd_mixer_selem_has_capture_channel(elem, supported_channels[i]))
return TRUE;
}
return FALSE;
}
static bool has_more_than_front_capture_channels(snd_mixer_elem_t *elem)
{
unsigned int i;
for (i = 2; i < ARRAY_SIZE(supported_channels); ++i)
if (snd_mixer_selem_has_capture_channel(elem, supported_channels[i]))
return TRUE;
return FALSE;
}
static bool has_any_control_channel(snd_mixer_elem_t *elem,
const snd_mixer_selem_channel_id_t channels[2],
int (*has_channel)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t))
{
return has_channel(elem, channels[0]) ||
(channels[1] != SND_MIXER_SCHN_UNKNOWN && has_channel(elem, channels[1]));
}
static bool has_merged_cswitch(snd_mixer_elem_t *elem)
{
bool pvol, psw;
unsigned int i;
pvol = snd_mixer_selem_has_playback_volume(elem);
psw = snd_mixer_selem_has_playback_switch(elem);
if ((pvol || psw) &&
snd_mixer_selem_has_capture_switch(elem) &&
!snd_mixer_selem_has_capture_volume(elem)) {
if (snd_mixer_selem_has_capture_switch_joined(elem))
return TRUE;
else if (((pvol && snd_mixer_selem_has_playback_volume_joined(elem)) ||
(psw && snd_mixer_selem_has_playback_switch_joined(elem))) &&
has_more_than_front_capture_channels(elem))
return FALSE;
for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
if (has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_capture_channel) &&
!has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_playback_channel))
return FALSE;
}
return TRUE;
}
return FALSE;
}
static unsigned int get_playback_controls_count(snd_mixer_elem_t *elem)
{
unsigned int count = 0;
unsigned int i;
int has_vol, has_sw;
has_vol = snd_mixer_selem_has_playback_volume(elem);
has_sw = snd_mixer_selem_has_playback_switch(elem);
if (!has_vol && !has_sw)
return 0;
if ((!has_vol || snd_mixer_selem_has_playback_volume_joined(elem)) &&
(!has_sw || snd_mixer_selem_has_playback_switch_joined(elem)))
return 1;
for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
if (snd_mixer_selem_has_playback_channel(elem, control_channels[i][0]) ||
(control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
snd_mixer_selem_has_playback_channel(elem, control_channels[i][1])))
++count;
}
return count;
}
static unsigned int get_capture_controls_count(snd_mixer_elem_t *elem)
{
unsigned int count = 0;
unsigned int i;
int has_vol, has_sw;
has_vol = snd_mixer_selem_has_capture_volume(elem);
has_sw = snd_mixer_selem_has_capture_switch(elem);
if ((!has_vol && !has_sw) ||
(view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem)))
return 0;
if ((!has_vol || snd_mixer_selem_has_capture_volume_joined(elem)) &&
(!has_sw || snd_mixer_selem_has_capture_switch_joined(elem)))
return 1;
for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0]) ||
(control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
snd_mixer_selem_has_capture_channel(elem, control_channels[i][1])))
++count;
}
return count;
}
static unsigned int get_controls_count_for_elem(snd_mixer_elem_t *elem)
{
unsigned int p, c;
if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
return 0;
if (snd_mixer_selem_is_enumerated(elem)) {
switch (view_mode) {
case VIEW_MODE_PLAYBACK:
return snd_mixer_selem_is_enum_capture(elem) ? 0 : 1;
case VIEW_MODE_CAPTURE:
return snd_mixer_selem_is_enum_capture(elem) ? 1 : 0;
case VIEW_MODE_ALL:
default:
return 1;
}
}
switch (view_mode) {
case VIEW_MODE_PLAYBACK:
return get_playback_controls_count(elem);
case VIEW_MODE_CAPTURE:
return get_capture_controls_count(elem);
case VIEW_MODE_ALL:
default:
p = get_playback_controls_count(elem);
c = get_capture_controls_count(elem);
return has_merged_cswitch(elem) ? p : p + c;
}
}
static void create_name(struct control *control)
{
unsigned int index;
char *s;
index = snd_mixer_selem_get_index(control->elem);
if (index > 0)
control->name = casprintf("%s %u", snd_mixer_selem_get_name(control->elem), index);
else
control->name = cstrdup(snd_mixer_selem_get_name(control->elem));
while ((s = strstr(control->name, "IEC958")) != NULL)
memcpy(s, "S/PDIF", 6);
}
static unsigned int create_controls_for_elem(snd_mixer_elem_t *elem, struct control *control)
{
unsigned int count = 0;
unsigned int i;
unsigned int enum_index;
struct control *front_control = NULL;
bool has_pvol, has_psw;
bool has_cvol, has_csw;
bool has_channel[LAST_SUPPORTED_CHANNEL + 1];
bool merged_cswitch;
bool has_ch0, has_ch1;
if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
return 0;
if (snd_mixer_selem_is_enumerated(elem)) {
if ((view_mode == VIEW_MODE_PLAYBACK && snd_mixer_selem_is_enum_capture(elem)) ||
(view_mode == VIEW_MODE_CAPTURE && !snd_mixer_selem_is_enum_capture(elem)))
return 0;
control->elem = elem;
control->flags = TYPE_ENUM;
control->enum_channel_bits = 0;
for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
if (snd_mixer_selem_get_enum_item(control->elem, (snd_mixer_selem_channel_id_t)i, &enum_index) >= 0)
control->enum_channel_bits |= 1 << i;
if (snd_mixer_selem_is_active(control->elem))
control->flags |= IS_ACTIVE;
create_name(control);
return 1;
}
has_pvol = snd_mixer_selem_has_playback_volume(elem);
has_psw = snd_mixer_selem_has_playback_switch(elem);
has_cvol = snd_mixer_selem_has_capture_volume(elem);
has_csw = snd_mixer_selem_has_capture_switch(elem);
merged_cswitch = view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem);
if (view_mode != VIEW_MODE_CAPTURE && (has_pvol || has_psw)) {
if ((!has_pvol || snd_mixer_selem_has_playback_volume_joined(elem)) &&
(!has_psw || snd_mixer_selem_has_playback_switch_joined(elem))) {
control->elem = elem;
if (has_pvol) {
control->flags |= TYPE_PVOLUME | HAS_VOLUME_0;
control->volume_channels[0] = 0;
}
if (has_psw) {
control->flags |= TYPE_PSWITCH | HAS_PSWITCH_0;
control->pswitch_channels[0] = 0;
}
if (merged_cswitch) {
control->flags |= TYPE_CSWITCH;
if (snd_mixer_selem_has_capture_switch_joined(elem)) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = 0;
} else {
if (snd_mixer_selem_has_capture_channel(elem, control_channels[0][0])) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = control_channels[0][0];
}
if (control_channels[0][1] != SND_MIXER_SCHN_UNKNOWN &&
snd_mixer_selem_has_capture_channel(elem, control_channels[0][1])) {
control->flags |= HAS_CSWITCH_1;
control->cswitch_channels[1] = control_channels[0][1];
}
}
if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
control->cswitch_channels[0] = control->cswitch_channels[1];
}
}
if (snd_mixer_selem_is_active(control->elem))
control->flags |= IS_ACTIVE;
create_name(control);
++control;
++count;
} else {
for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
has_channel[supported_channels[i]] =
snd_mixer_selem_has_playback_channel(elem, supported_channels[i]);
for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
has_ch0 = has_channel[control_channels[i][0]];
has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
has_channel[control_channels[i][1]];
if (!has_ch0 && !has_ch1)
continue;
control->elem = elem;
if (has_pvol) {
control->flags |= TYPE_PVOLUME;
if (snd_mixer_selem_has_playback_volume_joined(elem)) {
control->flags |= HAS_VOLUME_0;
control->volume_channels[0] = 0;
} else {
if (has_ch0) {
control->flags |= HAS_VOLUME_0;
control->volume_channels[0] = control_channels[i][0];
}
if (has_ch1) {
control->flags |= HAS_VOLUME_1;
control->volume_channels[1] = control_channels[i][1];
}
}
}
if (has_psw) {
control->flags |= TYPE_PSWITCH;
if (snd_mixer_selem_has_playback_switch_joined(elem)) {
control->flags |= HAS_PSWITCH_0;
control->pswitch_channels[0] = 0;
} else {
if (has_ch0) {
control->flags |= HAS_PSWITCH_0;
control->pswitch_channels[0] = control_channels[i][0];
}
if (has_ch1) {
control->flags |= HAS_PSWITCH_1;
control->pswitch_channels[1] = control_channels[i][1];
}
}
}
if (merged_cswitch) {
control->flags |= TYPE_CSWITCH;
if (snd_mixer_selem_has_capture_switch_joined(elem)) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = 0;
} else {
if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0])) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = control_channels[i][0];
}
if (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
snd_mixer_selem_has_capture_channel(elem, control_channels[i][1])) {
control->flags |= HAS_CSWITCH_1;
control->cswitch_channels[1] = control_channels[i][1];
}
}
}
if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) {
control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1;
control->volume_channels[0] = control->volume_channels[1];
}
if ((control->flags & (HAS_PSWITCH_0 | HAS_PSWITCH_1)) == HAS_PSWITCH_1) {
control->flags ^= HAS_PSWITCH_0 | HAS_PSWITCH_1;
control->pswitch_channels[0] = control->pswitch_channels[1];
}
if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
control->cswitch_channels[0] = control->cswitch_channels[1];
}
if (snd_mixer_selem_is_active(control->elem))
control->flags |= IS_ACTIVE;
create_name(control);
if (i == 0)
front_control = control;
else {
front_control->flags |= IS_MULTICH | 0;
control->flags |= IS_MULTICH | i;
}
++control;
++count;
}
}
}
if (view_mode != VIEW_MODE_PLAYBACK && (has_cvol || has_csw) && !merged_cswitch) {
if ((!has_cvol || snd_mixer_selem_has_capture_volume_joined(elem)) &&
(!has_csw || snd_mixer_selem_has_capture_switch_joined(elem))) {
control->elem = elem;
if (has_cvol) {
control->flags |= TYPE_CVOLUME | HAS_VOLUME_0;
control->volume_channels[0] = 0;
}
if (has_csw) {
control->flags |= TYPE_CSWITCH | HAS_CSWITCH_0;
control->cswitch_channels[0] = 0;
}
if (snd_mixer_selem_is_active(control->elem))
control->flags |= IS_ACTIVE;
create_name(control);
++control;
++count;
} else {
for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
has_channel[supported_channels[i]] =
snd_mixer_selem_has_capture_channel(elem, supported_channels[i]);
for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
has_ch0 = has_channel[control_channels[i][0]];
has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
has_channel[control_channels[i][1]];
if (!has_ch0 && !has_ch1)
continue;
control->elem = elem;
if (has_cvol) {
control->flags |= TYPE_CVOLUME;
if (snd_mixer_selem_has_capture_volume_joined(elem)) {
control->flags |= HAS_VOLUME_0;
control->volume_channels[0] = 0;
} else {
if (has_ch0) {
control->flags |= HAS_VOLUME_0;
control->volume_channels[0] = control_channels[i][0];
}
if (has_ch1) {
control->flags |= HAS_VOLUME_1;
control->volume_channels[1] = control_channels[i][1];
}
}
}
if (has_csw) {
control->flags |= TYPE_CSWITCH;
if (snd_mixer_selem_has_capture_switch_joined(elem)) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = 0;
} else {
if (has_ch0) {
control->flags |= HAS_CSWITCH_0;
control->cswitch_channels[0] = control_channels[i][0];
}
if (has_ch1) {
control->flags |= HAS_CSWITCH_1;
control->cswitch_channels[1] = control_channels[i][1];
}
}
}
if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) {
control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1;
control->volume_channels[0] = control->volume_channels[1];
}
if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
control->cswitch_channels[0] = control->cswitch_channels[1];
}
if (snd_mixer_selem_is_active(control->elem))
control->flags |= IS_ACTIVE;
create_name(control);
if (i == 0)
front_control = control;
else {
front_control->flags |= IS_MULTICH | 0;
control->flags |= IS_MULTICH | i;
}
++control;
++count;
}
}
}
return count;
}
static void search_for_focus_control(void)
{
snd_mixer_elem_t *elem;
unsigned int i;
elem = snd_mixer_find_selem(mixer, current_selem_id);
if (elem)
for (i = 0; i < controls_count; ++i)
if (controls[i].elem == elem) {
focus_control_index = i;
for (;;) {
++i;
if (i >= controls_count || controls[i].elem != elem)
return;
if (controls[i].flags == current_control_flags) {
focus_control_index = i;
return;
}
}
}
focus_control_index = 0;
}
void free_controls(void)
{
unsigned int i;
for (i = 0; i < controls_count; ++i)
free(controls[i].name);
free(controls);
controls = NULL;
controls_count = 0;
}
void create_controls(void)
{
snd_mixer_elem_t *elem;
struct control *control;
free_controls();
for (elem = snd_mixer_first_elem(mixer);
elem;
elem = snd_mixer_elem_next(elem))
controls_count += get_controls_count_for_elem(elem);
if (controls_count > 0) {
controls = ccalloc(controls_count, sizeof *controls);
control = controls;
for (elem = snd_mixer_first_elem(mixer);
elem;
elem = snd_mixer_elem_next(elem))
control += create_controls_for_elem(elem, control);
assert(control == controls + controls_count);
}
compute_controls_layout();
display_view_mode();
search_for_focus_control();
refocus_control();
}

View file

@ -0,0 +1,37 @@
#ifndef MIXER_CONTROLS_H_INCLUDED
#define MIXER_CONTROLS_H_INCLUDED
#include <alsa/asoundlib.h>
struct control {
snd_mixer_elem_t *elem;
char *name;
unsigned int flags;
#define TYPE_PVOLUME (1u << 4)
#define TYPE_CVOLUME (1u << 5)
#define TYPE_PSWITCH (1u << 6)
#define TYPE_CSWITCH (1u << 7)
#define TYPE_ENUM (1u << 8)
#define HAS_VOLUME_0 (1u << 9)
#define HAS_VOLUME_1 (1u << 10)
#define HAS_PSWITCH_0 (1u << 11)
#define HAS_PSWITCH_1 (1u << 12)
#define HAS_CSWITCH_0 (1u << 13)
#define HAS_CSWITCH_1 (1u << 14)
#define IS_MULTICH (1u << 15)
#define IS_ACTIVE (1u << 16)
#define MULTICH_MASK (0x0000f)
snd_mixer_selem_channel_id_t volume_channels[2];
snd_mixer_selem_channel_id_t pswitch_channels[2];
snd_mixer_selem_channel_id_t cswitch_channels[2];
unsigned int enum_channel_bits;
};
extern struct control *controls;
extern unsigned int controls_count;
bool are_there_any_controls(void);
void create_controls(void);
void free_controls(void);
#endif

763
alsamixer/mixer_display.c Normal file
View file

@ -0,0 +1,763 @@
/*
* mixer_display.c - handles displaying of mixer widget and controls
* Copyright (c) 1874 Lewis Carroll
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _C99_SOURCE /* lrint() */
#include "aconfig.h"
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <math.h>
#include CURSESINC
#include <alsa/asoundlib.h>
#include "gettext_curses.h"
#include "utils.h"
#include "mem.h"
#include "colors.h"
#include "widget.h"
#include "volume_mapping.h"
#include "mixer_widget.h"
#include "mixer_controls.h"
#include "mixer_display.h"
#include "mixer_clickable.h"
enum align {
ALIGN_LEFT,
ALIGN_RIGHT,
ALIGN_CENTER,
};
static bool screen_too_small;
static bool has_info_items;
static int info_items_left;
static int info_items_width;
static int visible_controls;
static int first_visible_control_index;
static int first_control_x;
static int control_width;
static int control_name_width;
static int base_y;
static int volume_height;
static int cswitch_y;
static int values_y;
static int name_y;
static int channel_name_y;
static void display_string_in_field(int y, int x, const char *s, int width, enum align align)
{
int string_width;
const char *s_end;
int spaces;
int cur_y, cur_x;
wmove(mixer_widget.window, y, x);
string_width = width;
s_end = mbs_at_width(s, &string_width, -1);
if (string_width >= width) {
waddnstr(mixer_widget.window, s, s_end - s);
} else {
if (align != ALIGN_LEFT) {
spaces = width - string_width;
if (align == ALIGN_CENTER)
spaces /= 2;
if (spaces > 0)
wprintw(mixer_widget.window, "%*s", spaces, "");
}
waddstr(mixer_widget.window, s);
if (align != ALIGN_RIGHT) {
getyx(mixer_widget.window, cur_y, cur_x);
if (cur_y == y) {
spaces = x + width - cur_x;
if (spaces > 0)
wprintw(mixer_widget.window, "%*s", spaces, "");
}
}
}
}
void init_mixer_layout(void)
{
const char *labels_left[4] = {
_("Card:"),
_("Chip:"),
_("View:"),
_("Item:"),
};
const char *labels_right[4] = {
_("F1: Help"),
_("F2: System information"),
_("F6: Select sound card"),
_("Esc: Exit"),
};
int label_width_left, label_width_right;
int right_x, i;
clickable_clear(0, 0, -1, -1);
screen_too_small = screen_lines < 14 || screen_cols < 12;
has_info_items = screen_lines >= 6;
if (!has_info_items)
return;
label_width_left = get_max_mbs_width(labels_left, 4);
label_width_right = get_max_mbs_width(labels_right, 4);
if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols)
label_width_right = 0;
if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols)
label_width_left = 0;
info_items_left = label_width_left ? 3 + label_width_left : 2;
right_x = screen_cols - label_width_right - 2;
info_items_width = right_x - info_items_left;
if (info_items_width < 1) {
has_info_items = FALSE;
return;
}
wattrset(mixer_widget.window, attrs.mixer_text);
if (label_width_left)
for (i = 0; i < 4; ++i)
display_string_in_field(1 + i, 2, labels_left[i],
label_width_left, ALIGN_RIGHT);
if (label_width_right)
for (i = 0; i < 4; ++i) {
display_string_in_field(1 + i, right_x, labels_right[i],
label_width_right, ALIGN_LEFT);
clickable_set(1 + i, right_x, 1 + i, right_x + label_width_right - 1,
CMD_MIXER_HELP + i, -1);
}
}
void display_card_info(void)
{
snd_hctl_t *hctl;
snd_ctl_t *ctl;
snd_ctl_card_info_t *card_info;
const char *card_name = NULL;
const char *mixer_name = NULL;
int err;
if (!has_info_items)
return;
snd_ctl_card_info_alloca(&card_info);
if (mixer_device_name)
err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
else
err = -1;
if (err >= 0) {
ctl = snd_hctl_ctl(hctl);
err = snd_ctl_card_info(ctl, card_info);
if (err >= 0) {
card_name = snd_ctl_card_info_get_name(card_info);
mixer_name = snd_ctl_card_info_get_mixername(card_info);
}
}
if (card_name)
wattrset(mixer_widget.window, attrs.mixer_active);
else {
wattrset(mixer_widget.window, attrs.mixer_text);
if (unplugged)
card_name = _("(unplugged)");
else
card_name = "-";
}
display_string_in_field(1, info_items_left, card_name, info_items_width, ALIGN_LEFT);
if (mixer_name)
wattrset(mixer_widget.window, attrs.mixer_active);
else {
wattrset(mixer_widget.window, attrs.mixer_text);
mixer_name = "-";
}
display_string_in_field(2, info_items_left, mixer_name, info_items_width, ALIGN_LEFT);
}
void display_view_mode(void)
{
const char *modes[3] = {
_("Playback"),
_("Capture"),
_("All"),
};
int widths[3];
bool has_view_mode;
int i;
clickable_clear(3, 0, 3, 30);
if (!has_info_items)
return;
has_view_mode = controls_count > 0 || are_there_any_controls();
for (i = 0; i < 3; ++i)
widths[i] = get_mbs_width(modes[i]);
if (4 + widths[0] + 6 + widths[1] + 6 + widths[2] + 1 <= info_items_width) {
wmove(mixer_widget.window, 3, info_items_left - 1);
wattrset(mixer_widget.window, attrs.mixer_text);
for (i = 0; i < 3; ++i) {
wprintw(mixer_widget.window, " F%c:", '3' + i);
if (has_view_mode && (int)view_mode == i) {
wattrset(mixer_widget.window, attrs.mixer_active);
wprintw(mixer_widget.window, "[%s]", modes[i]);
wattrset(mixer_widget.window, attrs.mixer_text);
} else {
wprintw(mixer_widget.window, " %s ", modes[i]);
}
clickable_set_relative(mixer_widget.window, 0, -(widths[i] + 5), 0, -1,
CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, i), -1);
}
} else {
wattrset(mixer_widget.window, attrs.mixer_active);
display_string_in_field(3, info_items_left,
has_view_mode ? modes[view_mode] : "",
info_items_width, ALIGN_LEFT);
}
}
static char *format_gain(long db)
{
if (db != SND_CTL_TLV_DB_GAIN_MUTE)
return casprintf("%.2f", db / 100.0);
else
return cstrdup(_("mute"));
}
static void display_focus_item_info(void)
{
struct control *control;
unsigned int index;
char buf[64];
long db, db2;
int sw, sw2;
char *dbs, *dbs2;
char *value_info;
char *item_info;
int err;
if (!has_info_items)
return;
wattrset(mixer_widget.window, attrs.mixer_active);
if (!controls_count || screen_too_small) {
display_string_in_field(4, info_items_left, "", info_items_width, ALIGN_LEFT);
return;
}
control = &controls[focus_control_index];
value_info = NULL;
if (control->flags & TYPE_ENUM) {
err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index);
if (err >= 0)
err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf);
if (err >= 0)
value_info = casprintf(" [%s]", buf);
} else if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
if (control->flags & TYPE_PVOLUME)
get_vol_func = snd_mixer_selem_get_playback_dB;
else
get_vol_func = snd_mixer_selem_get_capture_dB;
if (!(control->flags & HAS_VOLUME_1)) {
err = get_vol_func(control->elem, control->volume_channels[0], &db);
if (err >= 0) {
dbs = format_gain(db);
value_info = casprintf(" [%s %s]", _("dB gain:"), dbs);
free(dbs);
}
} else {
err = get_vol_func(control->elem, control->volume_channels[0], &db);
if (err >= 0)
err = get_vol_func(control->elem, control->volume_channels[1], &db2);
if (err >= 0) {
dbs = format_gain(db);
dbs2 = format_gain(db2);
value_info = casprintf(_(" [%s %s, %s]"), _("dB gain:"), dbs, dbs2);
free(dbs);
free(dbs2);
}
}
} else if (control->flags & TYPE_PSWITCH) {
if (!(control->flags & HAS_PSWITCH_1)) {
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw);
if (err >= 0 && !sw)
value_info = casprintf(" [%s]", _("Off"));
} else {
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw);
if (err >= 0)
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &sw2);
if (err >= 0 && (!sw || !sw2))
value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off"));
}
} else if (control->flags & TYPE_CSWITCH) {
if (!(control->flags & HAS_CSWITCH_1)) {
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw);
if (err >= 0 && !sw)
value_info = casprintf(" [%s]", _("Off"));
} else {
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw);
if (err >= 0)
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &sw2);
if (err >= 0 && (!sw || !sw2))
value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off"));
}
}
item_info = casprintf("%s%s", control->name, value_info ? value_info : "");
free(value_info);
display_string_in_field(4, info_items_left, item_info, info_items_width, ALIGN_LEFT);
free(item_info);
}
static void clear_controls_display(void)
{
int i;
clickable_clear(5, 0, -1, -1);
wattrset(mixer_widget.window, attrs.mixer_frame);
for (i = 5; i < screen_lines - 1; ++i)
mvwprintw(mixer_widget.window, i, 1, "%*s", screen_cols - 2, "");
}
static void center_string(int line, const char *s)
{
int width = get_mbs_width(s);
if (width <= screen_cols - 2)
mvwaddstr(mixer_widget.window, line, (screen_cols - width) / 2, s);
}
static void display_unplugged(void)
{
int lines, top, left;
bool boojum;
lines = screen_lines - 6;
if (lines < 2)
return;
top = lines / 2;
boojum = lines >= 10 && screen_cols >= 48;
top -= boojum ? 5 : 1;
if (top < 5)
top = 5;
if (boojum) {
left = (screen_cols - 46) / 2;
wattrset(mixer_widget.window, attrs.mixer_text);
mvwaddstr(mixer_widget.window, top + 0, left, "In the midst of the word he was trying to say,");
mvwaddstr(mixer_widget.window, top + 1, left + 2, "In the midst of his laughter and glee,");
mvwaddstr(mixer_widget.window, top + 2, left, "He had softly and suddenly vanished away---");
mvwaddstr(mixer_widget.window, top + 3, left + 2, "For the Snark was a Boojum, you see.");
mvwchgat(mixer_widget.window, top + 3, left + 16, 3, /* ^^^ */
attrs.mixer_text | A_BOLD, PAIR_NUMBER(attrs.mixer_text), NULL);
mvwaddstr(mixer_widget.window, top + 5, left, "(Lewis Carroll, \"The Hunting of the Snark\")");
top += 8;
}
wattrset(mixer_widget.window, attrs.errormsg);
center_string(top, _("The sound device was unplugged."));
center_string(top + 1, _("Press F6 to select another sound card."));
}
static void display_no_controls(void)
{
int y;
const char *msg;
y = (screen_lines - 6) / 2 - 1;
if (y < 5)
y = 5;
if (y >= screen_lines - 1)
return;
wattrset(mixer_widget.window, attrs.infomsg);
if (view_mode == VIEW_MODE_PLAYBACK && are_there_any_controls())
msg = _("This sound device does not have any playback controls.");
else if (view_mode == VIEW_MODE_CAPTURE && are_there_any_controls())
msg = _("This sound device does not have any capture controls.");
else
msg = _("This sound device does not have any controls.");
center_string(y, msg);
}
static void display_string_centered_in_control(int y, int col, const char *s, int width)
{
int left, x;
left = first_control_x + col * (control_width + 1);
x = left + (control_width - width) / 2;
display_string_in_field(y, x, s, width, ALIGN_CENTER);
}
static void display_control(unsigned int control_index)
{
struct control *control;
int col;
int i, c;
int left, frame_left;
int bar_height;
double volumes[2];
int switches[2];
unsigned int index;
const char *s;
char buf[64];
int err;
control = &controls[control_index];
col = control_index - first_visible_control_index;
left = first_control_x + col * (control_width + 1);
frame_left = left + (control_width - 4) / 2;
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attrs.ctl_frame);
else
wattrset(mixer_widget.window, attrs.ctl_inactive);
if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
mvwaddch(mixer_widget.window, base_y - volume_height - 1, frame_left, ACS_ULCORNER);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_URCORNER);
mvwvline(mixer_widget.window, base_y - volume_height, frame_left, ACS_VLINE, volume_height);
mvwvline(mixer_widget.window, base_y - volume_height, frame_left + 3, ACS_VLINE, volume_height);
mvwaddch(mixer_widget.window, base_y, frame_left,
control->flags & TYPE_PSWITCH ? ACS_LTEE : ACS_LLCORNER);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window,
control->flags & TYPE_PSWITCH ? ACS_RTEE : ACS_LRCORNER);
} else if (control->flags & TYPE_PSWITCH) {
mvwaddch(mixer_widget.window, base_y, frame_left, ACS_ULCORNER);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_URCORNER);
}
if (control->flags & TYPE_PSWITCH) {
mvwaddch(mixer_widget.window, base_y + 1, frame_left, ACS_VLINE);
mvwaddch(mixer_widget.window, base_y + 1, frame_left + 3, ACS_VLINE);
mvwaddch(mixer_widget.window, base_y + 2, frame_left, ACS_LLCORNER);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_HLINE);
waddch(mixer_widget.window, ACS_LRCORNER);
}
if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
double (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t);
if (control->flags & TYPE_PVOLUME)
get_vol_func = get_normalized_playback_volume;
else
get_vol_func = get_normalized_capture_volume;
volumes[0] = get_vol_func(control->elem, control->volume_channels[0]);
if (control->flags & HAS_VOLUME_1)
volumes[1] = get_vol_func(control->elem, control->volume_channels[1]);
else
volumes[1] = volumes[0];
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, 0);
for (c = 0; c < 2; c++) {
bar_height = lrint(volumes[c] * volume_height);
for (i = 0; i < volume_height; ++i) {
chtype ch;
if (i + 1 > bar_height)
ch = ' ' | (control->flags & IS_ACTIVE ?
attrs.ctl_frame : 0);
else {
ch = ACS_CKBOARD;
if (!(control->flags & IS_ACTIVE))
;
#ifdef TRICOLOR_VOLUME_BAR
else if (i > volume_height * 8 / 10)
ch |= attrs.ctl_bar_hi;
else if (i > volume_height * 4 / 10)
ch |= attrs.ctl_bar_mi;
#endif
else
ch |= attrs.ctl_bar_lo;
}
mvwaddch(mixer_widget.window, base_y - i - 1,
frame_left + c + 1, ch);
}
}
clickable_set(base_y - volume_height, frame_left + 1, base_y, frame_left + 2,
CMD_MIXER_MOUSE_CLICK_VOLUME_BAR, control_index);
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attrs.mixer_active);
if (!(control->flags & HAS_VOLUME_1)) {
sprintf(buf, "%d", (int)lrint(volumes[0] * 100));
display_string_in_field(values_y, frame_left - 2, buf, 8, ALIGN_CENTER);
} else {
mvwprintw(mixer_widget.window, values_y, frame_left - 2,
"%3d", (int)lrint(volumes[0] * 100));
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attrs.ctl_frame);
waddstr(mixer_widget.window, "<>");
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attrs.mixer_active);
wprintw(mixer_widget.window, "%-3d", (int)lrint(volumes[1] * 100));
}
}
if (control->flags & TYPE_PSWITCH) {
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &switches[0]);
if (err >= 0 && (control->flags & HAS_PSWITCH_1))
err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &switches[1]);
else
switches[1] = switches[0];
if (err < 0)
return;
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, 0);
mvwaddch(mixer_widget.window, base_y + 1, frame_left + 1,
switches[0]
/* TRANSLATORS: playback on; one character */
? _("O")[0] | (control->flags & IS_ACTIVE ? attrs.ctl_nomute : 0)
/* TRANSLATORS: playback muted; one character */
: _("M")[0] | (control->flags & IS_ACTIVE ? attrs.ctl_mute : 0));
waddch(mixer_widget.window,
switches[1]
? _("O")[0] | (control->flags & IS_ACTIVE ? attrs.ctl_nomute : 0)
: _("M")[0] | (control->flags & IS_ACTIVE ? attrs.ctl_mute : 0));
clickable_set(base_y + 1, frame_left + 1, base_y + 1, frame_left + 2,
CMD_MIXER_MOUSE_CLICK_MUTE, control_index);
}
if (control->flags & TYPE_CSWITCH) {
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &switches[0]);
if (err >= 0 && (control->flags & HAS_CSWITCH_1))
err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &switches[1]);
else
switches[1] = switches[0];
if (err < 0)
return;
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, switches[0] ? attrs.ctl_capture : attrs.ctl_nocapture);
/* TRANSLATORS: "left"; no more than two characters */
display_string_in_field(cswitch_y - 1, frame_left - 2, switches[0] ? _("L") : "", 2, ALIGN_RIGHT);
clickable_set(cswitch_y - 1, frame_left - 2, cswitch_y - 1, frame_left - 1,
CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, LEFT), control_index);
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, switches[1] ? attrs.ctl_capture : attrs.ctl_nocapture);
/* TRANSLATORS: "right"; no more than two characters */
display_string_in_field(cswitch_y - 1, frame_left + 4, switches[1] ? _("R") : "", 2, ALIGN_LEFT);
clickable_set(cswitch_y - 1, frame_left + 4, cswitch_y - 1, frame_left + 5,
CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, RIGHT), control_index);
/* TRANSLATORS: no more than eight characters */
s = _("CAPTURE");
if (switches[0] || switches[1]) {
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attrs.ctl_capture);
display_string_in_field(cswitch_y, frame_left - 2, s, 8, ALIGN_CENTER);
} else {
i = get_mbs_width(s);
if (i > 8)
i = 8;
memset(buf, '-', i);
buf[i] = '\0';
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attrs.ctl_nocapture);
display_string_in_field(cswitch_y, frame_left - 2, buf, 8, ALIGN_CENTER);
}
clickable_set(cswitch_y, frame_left - 2, cswitch_y, frame_left - 2 + 8,
CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, LEFT|RIGHT), control_index);
}
if (control->flags & TYPE_ENUM) {
err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index);
if (err < 0)
return;
err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf);
if (err < 0)
return;
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attrs.mixer_active);
display_string_centered_in_control(base_y, col, buf, control_width);
clickable_set_relative(mixer_widget.window, 0, -control_name_width, 0, -2,
CMD_MIXER_MOUSE_CLICK_CONTROL_ENUM, control_index);
}
if ((int)control_index == focus_control_index) {
i = first_control_x + col * (control_width + 1) + (control_width - control_name_width) / 2;
wattrset(mixer_widget.window, attrs.ctl_mark_focus);
mvwaddch(mixer_widget.window, name_y, i - 1, '<');
mvwaddch(mixer_widget.window, name_y, i + control_name_width, '>');
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attrs.ctl_label_focus);
else
wattrset(mixer_widget.window, attrs.ctl_label_inactive);
} else {
if (control->flags & IS_ACTIVE)
wattrset(mixer_widget.window, attrs.ctl_label);
else
wattrset(mixer_widget.window, attrs.ctl_label_inactive);
}
display_string_centered_in_control(name_y, col, control->name, control_name_width);
clickable_set_relative(mixer_widget.window, -1, -control_name_width, 0, -2,
CMD_WITH_ARG(CMD_MIXER_FOCUS_CONTROL, control_index), -1);
if (channel_name_y > name_y) {
if (control->flags & IS_MULTICH) {
switch (control->flags & MULTICH_MASK) {
case 0:
default:
s = _("Front");
break;
case 1:
s = _("Rear");
break;
case 2:
s = _("Center");
break;
case 3:
s = _("Woofer");
break;
case 4:
s = _("Side");
break;
}
} else {
s = "";
wattrset(mixer_widget.window, attrs.mixer_frame);
}
display_string_centered_in_control(channel_name_y, col, s,
control_name_width);
}
}
static void display_scroll_indicators(void)
{
int y0, y1;
chtype left, right;
if (screen_too_small)
return;
y0 = screen_lines * 3 / 8;
y1 = screen_lines * 5 / 8;
left = first_visible_control_index > 0 ? ACS_LARROW : ACS_VLINE;
right = first_visible_control_index + visible_controls < (int)controls_count
? ACS_RARROW : ACS_VLINE;
wattrset(mixer_widget.window, attrs.mixer_frame);
mvwvline(mixer_widget.window, y0, 0, left, y1 - y0 + 1);
mvwvline(mixer_widget.window, y0, screen_cols -1, right, y1 - y0 + 1);
clickable_set(y0, 0, y1, 0,
CMD_WITH_ARG(CMD_MIXER_PREVIOUS, visible_controls), -1);
clickable_set(y0, screen_cols - 1, y1, screen_cols - 1,
CMD_WITH_ARG(CMD_MIXER_NEXT, visible_controls), -1);
}
void display_controls(void)
{
int i;
if (first_visible_control_index > (int)controls_count - visible_controls)
first_visible_control_index = controls_count - visible_controls;
if (first_visible_control_index > focus_control_index)
first_visible_control_index = focus_control_index;
else if (first_visible_control_index < focus_control_index - visible_controls + 1 && visible_controls)
first_visible_control_index = focus_control_index - visible_controls + 1;
clear_controls_display();
display_focus_item_info();
if (controls_count > 0) {
if (!screen_too_small)
for (i = 0; i < visible_controls; ++i)
display_control(first_visible_control_index + i);
} else if (unplugged) {
display_unplugged();
} else if (mixer_device_name) {
display_no_controls();
}
display_scroll_indicators();
}
void compute_controls_layout(void)
{
bool any_volume, any_pswitch, any_cswitch, any_multich;
int max_width, name_len;
int height, space;
unsigned int i;
if (controls_count == 0 || screen_too_small) {
visible_controls = 0;
return;
}
any_volume = FALSE;
any_pswitch = FALSE;
any_cswitch = FALSE;
any_multich = FALSE;
for (i = 0; i < controls_count; ++i) {
if (controls[i].flags & (TYPE_PVOLUME | TYPE_CVOLUME))
any_volume = 1;
if (controls[i].flags & TYPE_PSWITCH)
any_pswitch = 1;
if (controls[i].flags & TYPE_CSWITCH)
any_cswitch = 1;
if (controls[i].flags & IS_MULTICH)
any_multich = 1;
}
max_width = 8;
for (i = 0; i < controls_count; ++i) {
name_len = strlen(controls[i].name);
if (name_len > max_width)
max_width = name_len;
}
max_width = (max_width + 1) & ~1;
control_width = (screen_cols - 3 - (int)controls_count) / controls_count;
if (control_width < 8)
control_width = 8;
if (control_width > max_width)
control_width = max_width;
if (control_width > screen_cols - 4)
control_width = screen_cols - 4;
visible_controls = (screen_cols - 3) / (control_width + 1);
if (visible_controls > (int)controls_count)
visible_controls = controls_count;
first_control_x = 2 + (screen_cols - 3 - visible_controls * (control_width + 1)) / 2;
if (control_width < max_width)
control_name_width = control_width;
else
control_name_width = max_width;
height = 2;
if (any_volume)
height += 2;
if (any_pswitch)
height += 2;
if (any_cswitch)
height += 1;
if (any_multich)
height += 1;
if (any_volume) {
space = screen_lines - 6 - height;
if (space <= 1)
volume_height = 1;
else if (space <= 10)
volume_height = space;
else
volume_height = 10 + (space - 10) / 2;
height += volume_height;
}
space = screen_lines - 6 - height;
channel_name_y = screen_lines - 2 - space / 2;
name_y = channel_name_y - any_multich;
values_y = name_y - any_volume;
cswitch_y = values_y - any_cswitch;
base_y = cswitch_y - 1 - 2 * any_pswitch;
}

10
alsamixer/mixer_display.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef MIXER_DISPLAY_H_INCLUDED
#define MIXER_DISPLAY_H_INCLUDED
void init_mixer_layout(void);
void display_card_info(void);
void display_view_mode(void);
void display_controls(void);
void compute_controls_layout(void);
#endif

674
alsamixer/mixer_widget.c Normal file
View file

@ -0,0 +1,674 @@
/*
* mixer_widget.c - mixer widget and keys handling
* Copyright (c) 1998,1999 Tim Janik
* Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <alsa/asoundlib.h>
#include "gettext_curses.h"
#include "version.h"
#include "utils.h"
#include "die.h"
#include "mem.h"
#include "colors.h"
#include "widget.h"
#include "textbox.h"
#include "proc_files.h"
#include "card_select.h"
#include "volume_mapping.h"
#include "mixer_clickable.h"
#include "mixer_controls.h"
#include "mixer_display.h"
#include "mixer_widget.h"
#include "bindings.h"
snd_mixer_t *mixer;
char *mixer_device_name;
bool unplugged;
struct widget mixer_widget;
enum view_mode view_mode;
int focus_control_index;
snd_mixer_selem_id_t *current_selem_id;
unsigned int current_control_flags;
bool control_values_changed;
bool controls_changed;
unsigned int mouse_wheel_step = 1;
bool mouse_wheel_focuses_control = 1;
static int elem_callback(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED, unsigned int mask)
{
if (mask == SND_CTL_EVENT_MASK_REMOVE) {
controls_changed = TRUE;
} else {
if (mask & SND_CTL_EVENT_MASK_VALUE)
control_values_changed = TRUE;
if (mask & SND_CTL_EVENT_MASK_INFO)
controls_changed = TRUE;
}
return 0;
}
static int mixer_callback(snd_mixer_t *mixer ATTRIBUTE_UNUSED, unsigned int mask, snd_mixer_elem_t *elem)
{
if (mask & SND_CTL_EVENT_MASK_ADD) {
snd_mixer_elem_set_callback(elem, elem_callback);
controls_changed = TRUE;
}
return 0;
}
void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt)
{
int err;
err = snd_mixer_open(&mixer, 0);
if (err < 0)
fatal_alsa_error(_("cannot open mixer"), err);
mixer_device_name = cstrdup(selem_regopt->device);
err = snd_mixer_selem_register(mixer, selem_regopt, NULL);
if (err < 0)
fatal_alsa_error(_("cannot open mixer"), err);
snd_mixer_set_callback(mixer, mixer_callback);
err = snd_mixer_load(mixer);
if (err < 0)
fatal_alsa_error(_("cannot load mixer controls"), err);
err = snd_mixer_selem_id_malloc(&current_selem_id);
if (err < 0)
fatal_error("out of memory");
}
static void set_view_mode(enum view_mode m)
{
view_mode = m;
create_controls();
}
static void close_hctl(void)
{
free_controls();
if (mixer_device_name) {
snd_mixer_detach(mixer, mixer_device_name);
free(mixer_device_name);
mixer_device_name = NULL;
}
}
static void check_unplugged(void)
{
snd_hctl_t *hctl;
snd_ctl_t *ctl;
unsigned int state;
int err;
unplugged = FALSE;
if (mixer_device_name) {
err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
if (err >= 0) {
ctl = snd_hctl_ctl(hctl);
/* just any random function that does an ioctl() */
err = snd_ctl_get_power_state(ctl, &state);
if (err == -ENODEV)
unplugged = TRUE;
}
}
}
void close_mixer_device(void)
{
check_unplugged();
close_hctl();
display_card_info();
set_view_mode(view_mode);
}
bool select_card_by_name(const char *device_name)
{
int err;
bool opened;
char *msg;
close_hctl();
unplugged = FALSE;
opened = FALSE;
if (device_name) {
err = snd_mixer_attach(mixer, device_name);
if (err >= 0)
opened = TRUE;
else {
msg = casprintf(_("Cannot open mixer device '%s'."), device_name);
show_alsa_error(msg, err);
free(msg);
}
}
if (opened) {
mixer_device_name = cstrdup(device_name);
err = snd_mixer_load(mixer);
if (err < 0)
fatal_alsa_error(_("cannot load mixer controls"), err);
}
display_card_info();
set_view_mode(view_mode);
return opened;
}
static void show_help(void)
{
const char *help[] = {
_("Esc Exit"),
_("F1 ? H Help"),
_("F2 / System information"),
_("F3 Show playback controls"),
_("F4 Show capture controls"),
_("F5 Show all controls"),
_("Tab Toggle view mode (F3/F4/F5)"),
_("F6 S Select sound card"),
_("L Redraw screen"),
"",
_("Left Move to the previous control"),
_("Right Move to the next control"),
"",
_("Up/Down Change volume"),
_("+ - Change volume"),
_("Page Up/Dn Change volume in big steps"),
_("End Set volume to 0%"),
_("0-9 Set volume to 0%-90%"),
_("Q W E Increase left/both/right volumes"),
/* TRANSLATORS: or Y instead of Z */
_("Z X C Decrease left/both/right volumes"),
_("B Balance left and right volumes"),
"",
_("M Toggle mute"),
/* TRANSLATORS: or , . */
_("< > Toggle left/right mute"),
"",
_("Space Toggle capture"),
/* TRANSLATORS: or Insert Delete */
_("; ' Toggle left/right capture"),
"",
_("Authors:"),
_(" Tim Janik"),
_(" Jaroslav Kysela <perex@perex.cz>"),
_(" Clemens Ladisch <clemens@ladisch.de>"),
};
show_text(help, ARRAY_SIZE(help), _("Help"));
}
void refocus_control(void)
{
if (focus_control_index >= 0 &&
focus_control_index < (int)controls_count) {
snd_mixer_selem_get_id(controls[focus_control_index].elem, current_selem_id);
current_control_flags = controls[focus_control_index].flags;
}
display_controls();
}
static struct control *get_focus_control(unsigned int type)
{
if (focus_control_index >= 0 &&
focus_control_index < (int)controls_count &&
(controls[focus_control_index].flags & IS_ACTIVE) &&
(controls[focus_control_index].flags & type))
return &controls[focus_control_index];
else
return NULL;
}
static void change_enum_to_percent(struct control *control, int value)
{
unsigned int i;
unsigned int index;
unsigned int new_index;
int items;
int err;
i = ffs(control->enum_channel_bits) - 1;
err = snd_mixer_selem_get_enum_item(control->elem, i, &index);
if (err < 0)
return;
new_index = index;
if (value == 0) {
new_index = 0;
} else if (value == 100) {
items = snd_mixer_selem_get_enum_items(control->elem);
if (items < 1)
return;
new_index = items - 1;
}
if (new_index == index)
return;
for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
if (control->enum_channel_bits & (1 << i))
snd_mixer_selem_set_enum_item(control->elem, i, new_index);
}
static void change_enum_relative(struct control *control, int delta)
{
int items;
unsigned int i;
unsigned int index;
int new_index;
int err;
items = snd_mixer_selem_get_enum_items(control->elem);
if (items < 1)
return;
err = snd_mixer_selem_get_enum_item(control->elem, 0, &index);
if (err < 0)
return;
new_index = (int)index + delta;
if (new_index < 0)
new_index = 0;
else if (new_index >= items)
new_index = items - 1;
if (new_index == (int)index)
return;
for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
if (control->enum_channel_bits & (1 << i))
snd_mixer_selem_set_enum_item(control->elem, i, new_index);
}
static void change_volume_to_percent(struct control *control, int value, unsigned int channels)
{
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int);
if (!(control->flags & HAS_VOLUME_1))
channels = LEFT;
if (control->flags & TYPE_PVOLUME)
set_func = set_normalized_playback_volume;
else
set_func = set_normalized_capture_volume;
if (channels & LEFT)
set_func(control->elem, control->volume_channels[0], value / 100.0, 0);
if (channels & RIGHT)
set_func(control->elem, control->volume_channels[1], value / 100.0, 0);
}
static double clamp_volume(double v)
{
if (v < 0)
return 0;
if (v > 1)
return 1;
return v;
}
static void change_volume_relative(struct control *control, int delta, unsigned int channels)
{
double (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t);
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int);
double left = 0, right = 0;
int dir;
if (!(control->flags & HAS_VOLUME_1))
channels = LEFT;
if (control->flags & TYPE_PVOLUME) {
get_func = get_normalized_playback_volume;
set_func = set_normalized_playback_volume;
} else {
get_func = get_normalized_capture_volume;
set_func = set_normalized_capture_volume;
}
if (channels & LEFT)
left = get_func(control->elem, control->volume_channels[0]);
if (channels & RIGHT)
right = get_func(control->elem, control->volume_channels[1]);
dir = delta > 0 ? 1 : -1;
if (channels & LEFT) {
left = clamp_volume(left + delta / 100.0);
set_func(control->elem, control->volume_channels[0], left, dir);
}
if (channels & RIGHT) {
right = clamp_volume(right + delta / 100.0);
set_func(control->elem, control->volume_channels[1], right, dir);
}
}
static void change_control_to_percent(int value, unsigned int channels)
{
struct control *control;
control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
if (!control)
return;
if (control->flags & TYPE_ENUM)
change_enum_to_percent(control, value);
else
change_volume_to_percent(control, value, channels);
display_controls();
}
static void change_control_relative(int delta, unsigned int channels)
{
struct control *control;
control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
if (!control)
return;
if (control->flags & TYPE_ENUM)
change_enum_relative(control, delta);
else
change_volume_relative(control, delta, channels);
display_controls();
}
static void toggle_switches(unsigned int type, unsigned int channels)
{
struct control *control;
unsigned int switch_1_mask;
int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *);
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int);
snd_mixer_selem_channel_id_t channel_ids[2];
int left, right;
int err;
control = get_focus_control(type);
if (!control)
return;
if (type == TYPE_PSWITCH) {
switch_1_mask = HAS_PSWITCH_1;
get_func = snd_mixer_selem_get_playback_switch;
set_func = snd_mixer_selem_set_playback_switch;
channel_ids[0] = control->pswitch_channels[0];
channel_ids[1] = control->pswitch_channels[1];
} else {
switch_1_mask = HAS_CSWITCH_1;
get_func = snd_mixer_selem_get_capture_switch;
set_func = snd_mixer_selem_set_capture_switch;
channel_ids[0] = control->cswitch_channels[0];
channel_ids[1] = control->cswitch_channels[1];
}
if (!(control->flags & switch_1_mask))
channels = LEFT;
if (channels & LEFT) {
err = get_func(control->elem, channel_ids[0], &left);
if (err < 0)
return;
}
if (channels & RIGHT) {
err = get_func(control->elem, channel_ids[1], &right);
if (err < 0)
return;
}
if (channels & LEFT)
set_func(control->elem, channel_ids[0], !left);
if (channels & RIGHT)
set_func(control->elem, channel_ids[1], !right);
display_controls();
}
static void toggle_mute(unsigned int channels)
{
toggle_switches(TYPE_PSWITCH, channels);
}
static void toggle_capture(unsigned int channels)
{
toggle_switches(TYPE_CSWITCH, channels);
}
static void balance_volumes(void)
{
struct control *control;
double left, right;
control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME);
if (!control || !(control->flags & HAS_VOLUME_1))
return;
if (control->flags & TYPE_PVOLUME) {
left = get_normalized_playback_volume(control->elem, control->volume_channels[0]);
right = get_normalized_playback_volume(control->elem, control->volume_channels[1]);
} else {
left = get_normalized_capture_volume(control->elem, control->volume_channels[0]);
right = get_normalized_capture_volume(control->elem, control->volume_channels[1]);
}
left = (left + right) / 2;
if (control->flags & TYPE_PVOLUME) {
set_normalized_playback_volume(control->elem, control->volume_channels[0], left, 0);
set_normalized_playback_volume(control->elem, control->volume_channels[1], left, 0);
} else {
set_normalized_capture_volume(control->elem, control->volume_channels[0], left, 0);
set_normalized_capture_volume(control->elem, control->volume_channels[1], left, 0);
}
display_controls();
}
static int on_mouse_key() {
MEVENT m;
command_enum cmd = 0;
unsigned int channels = LEFT | RIGHT;
unsigned int index;
struct control *control;
struct clickable_rect *rect;
if (getmouse(&m) == ERR)
return 0;
if (m.bstate & (
BUTTON1_PRESSED|BUTTON1_RELEASED|
BUTTON2_PRESSED|BUTTON2_RELEASED|
BUTTON3_PRESSED|BUTTON3_RELEASED))
return 0;
rect = clickable_find(m.y, m.x);
if (rect)
cmd = rect->command;
#if NCURSES_MOUSE_VERSION > 1
if (m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED|BUTTON5_CLICKED|BUTTON5_PRESSED)) {
switch (cmd) {
case CMD_MIXER_MOUSE_CLICK_CONTROL_ENUM:
focus_control_index = rect->arg1;
return CMD_WITH_ARG((
m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED)
? CMD_MIXER_CONTROL_UP
: CMD_MIXER_CONTROL_DOWN
), 1);
case CMD_MIXER_MOUSE_CLICK_VOLUME_BAR:
if (mouse_wheel_focuses_control)
focus_control_index = rect->arg1;
/* fall through */
default:
return CMD_WITH_ARG((
m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED)
? CMD_MIXER_CONTROL_UP
: CMD_MIXER_CONTROL_DOWN
), mouse_wheel_step);
}
}
#endif
/* If the rectangle has got an argument (value != -1) it is used for
* setting `focus_control_index` */
if (rect && rect->arg1 >= 0)
focus_control_index = rect->arg1;
switch (cmd) {
case CMD_MIXER_MOUSE_CLICK_VOLUME_BAR:
if (m.bstate & (BUTTON3_CLICKED|BUTTON3_DOUBLE_CLICKED|BUTTON3_TRIPLE_CLICKED))
channels = m.x - rect->x1 + 1;
return CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT_LEFT + channels - 1,
(100 * (rect->y2 - m.y) / (rect->y2 - rect->y1)) // volume
);
case CMD_MIXER_MOUSE_CLICK_MUTE:
if (m.bstate & (BUTTON3_CLICKED|BUTTON3_DOUBLE_CLICKED|BUTTON3_TRIPLE_CLICKED))
channels = m.x - rect->x1 + 1;
return CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, channels);
case CMD_MIXER_MOUSE_CLICK_CONTROL_ENUM:
control = get_focus_control(TYPE_ENUM);
if (control &&
(snd_mixer_selem_get_enum_item(control->elem, 0, &index) >= 0)) {
return (index == 0
? CMD_WITH_ARG(CMD_MIXER_CONTROL_UP, 100)
: CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN, 1));
}
break;
default:
return cmd; // non-mouse command
}
return 0; // failed mouse command
}
static void on_handle_key(int key)
{
int arg;
command_enum cmd;
if (key == KEY_MOUSE)
cmd = on_mouse_key();
else if (key < (int)ARRAY_SIZE(mixer_bindings))
cmd = mixer_bindings[key];
else
return;
arg = CMD_GET_ARGUMENT(cmd);
cmd = CMD_GET_COMMAND(cmd);
switch (cmd) {
case CMD_MIXER_CONTROL_DOWN_LEFT:
case CMD_MIXER_CONTROL_DOWN_RIGHT:
case CMD_MIXER_CONTROL_DOWN:
arg = (-arg);
/* fall through */
case CMD_MIXER_CONTROL_UP_LEFT:
case CMD_MIXER_CONTROL_UP_RIGHT:
case CMD_MIXER_CONTROL_UP:
change_control_relative(arg, cmd % 4);
break;
case CMD_MIXER_CONTROL_SET_PERCENT_LEFT:
case CMD_MIXER_CONTROL_SET_PERCENT_RIGHT:
case CMD_MIXER_CONTROL_SET_PERCENT:
change_control_to_percent(arg, cmd % 4);
break;
case CMD_MIXER_CLOSE:
mixer_widget.close();
break;
case CMD_MIXER_HELP:
show_help();
break;
case CMD_MIXER_SYSTEM_INFORMATION:
create_proc_files_list();
break;
case CMD_MIXER_TOGGLE_VIEW_MODE:
arg = (view_mode + 1) % VIEW_MODE_COUNT;
/* fall through */
case CMD_MIXER_SET_VIEW_MODE:
set_view_mode((enum view_mode)(arg));
break;
case CMD_MIXER_SELECT_CARD:
create_card_select_list();
break;
case CMD_MIXER_REFRESH:
clearok(mixer_widget.window, TRUE);
display_controls();
break;
case CMD_MIXER_PREVIOUS:
arg = (-arg);
/* fall through */
case CMD_MIXER_NEXT:
arg = focus_control_index + arg;
/* fall through */
case CMD_MIXER_FOCUS_CONTROL:
focus_control_index = arg;
if (focus_control_index < 0)
focus_control_index = 0;
else if (focus_control_index >= (int)controls_count)
focus_control_index = controls_count - 1;
refocus_control();
break;
case CMD_MIXER_TOGGLE_MUTE:
toggle_mute(arg);
break;
case CMD_MIXER_TOGGLE_CAPTURE:
toggle_capture(arg);
break;
case CMD_MIXER_BALANCE_CONTROL:
balance_volumes();
break;
}
}
static void create(void)
{
static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " ";
widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0,
attrs.mixer_frame, WIDGET_BORDER);
if (screen_cols >= (int)(sizeof(title) - 1) + 2) {
wattrset(mixer_widget.window, attrs.mixer_active);
mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title);
}
init_mixer_layout();
display_card_info();
set_view_mode(view_mode);
}
static void on_window_size_changed(void)
{
create();
}
static void on_close(void)
{
widget_free(&mixer_widget);
}
void mixer_shutdown(void)
{
free_controls();
if (mixer)
snd_mixer_close(mixer);
if (current_selem_id)
snd_mixer_selem_id_free(current_selem_id);
}
struct widget mixer_widget = {
.handle_key = on_handle_key,
.window_size_changed = on_window_size_changed,
.close = on_close,
};
void create_mixer_widget(void)
{
create();
}

45
alsamixer/mixer_widget.h Normal file
View file

@ -0,0 +1,45 @@
#ifndef MIXER_WIDGET_H_INCLUDED
#define MIXER_WIDGET_H_INCLUDED
#include CURSESINC
#include <alsa/asoundlib.h>
#include "widget.h"
enum view_mode {
VIEW_MODE_PLAYBACK,
VIEW_MODE_CAPTURE,
VIEW_MODE_ALL,
VIEW_MODE_COUNT,
};
enum channel_mask {
LEFT = 1,
RIGHT = 2,
};
extern snd_mixer_t *mixer;
extern char *mixer_device_name;
extern bool unplugged;
extern struct widget mixer_widget;
extern enum view_mode view_mode;
extern int focus_control_index;
extern snd_mixer_selem_id_t *current_selem_id;
extern unsigned int current_control_flags;
extern bool control_values_changed;
extern bool controls_changed;
extern unsigned int mouse_wheel_step;
extern bool mouse_wheel_focuses_control;
void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt);
void create_mixer_widget(void);
void mixer_shutdown(void);
void close_mixer_device(void);
bool select_card_by_name(const char *device_name);
void refocus_control(void);
#endif

107
alsamixer/proc_files.c Normal file
View file

@ -0,0 +1,107 @@
/*
* proc_files.c - shows ALSA system information files
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <assert.h>
#include <menu.h>
#include <unistd.h>
#include "gettext_curses.h"
#include "utils.h"
#include "die.h"
#include "mem.h"
#include "colors.h"
#include "widget.h"
#include "textbox.h"
#include "proc_files.h"
#include "menu_widget.h"
static struct widget proc_widget;
static ITEM *items[7];
static unsigned int items_count;
static MENU *menu;
static void on_handle_key(int key)
{
ITEM *item;
switch (menu_widget_handle_key(menu, key)) {
case KEY_ENTER:
item = current_item(menu);
if (item)
show_textfile(item_name(item));
break;
case KEY_CANCEL:
proc_widget.close();
break;
}
}
static void create(void)
{
menu_widget_create(&proc_widget, menu, _("Select File"));
}
static void on_close(void)
{
unsigned int i;
unpost_menu(menu);
free_menu(menu);
for (i = 0; i < items_count; ++i)
free_item(items[i]);
widget_free(&proc_widget);
}
static void add_item(const char *file_name)
{
if (access(file_name, F_OK) == 0) {
items[items_count] = new_item(file_name, NULL);
if (!items[items_count])
fatal_error("cannot create menu item");
++items_count;
assert(items_count < ARRAY_SIZE(items));
}
}
static struct widget proc_widget = {
.handle_key = on_handle_key,
.window_size_changed = create,
.close = on_close,
};
void create_proc_files_list(void)
{
items_count = 0;
add_item("/proc/asound/version");
add_item("/proc/asound/cards");
add_item("/proc/asound/devices");
add_item("/proc/asound/oss/devices");
add_item("/proc/asound/timers");
add_item("/proc/asound/pcm");
items[items_count] = NULL;
menu = new_menu(items);
if (!menu)
fatal_error("cannot create menu");
set_menu_fore(menu, attrs.menu_selected);
set_menu_back(menu, attrs.menu);
set_menu_mark(menu, NULL);
menu_opts_off(menu, O_SHOWDESC);
create();
}

6
alsamixer/proc_files.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef PROC_FILES_H_INCLUDED
#define PROC_FILES_H_INCLUDED
void create_proc_files_list(void);
#endif

335
alsamixer/textbox.c Normal file
View file

@ -0,0 +1,335 @@
/*
* textbox.c - show a text box for messages, files or help
* Copyright (c) 1998,1999 Tim Janik
* Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include CURSESINC
#include <alsa/asoundlib.h>
#include "gettext_curses.h"
#include "utils.h"
#include "die.h"
#include "mem.h"
#include "colors.h"
#include "widget.h"
#include "textbox.h"
#include "bindings.h"
static void create_text_box(const char *const *lines, unsigned int count,
const char *title, int attrs);
void show_error(const char *msg, int err)
{
const char *lines[2];
unsigned int count;
lines[0] = msg;
count = 1;
if (err) {
lines[1] = strerror(err);
count = 2;
}
create_text_box(lines, count, _("Error"), attrs.errormsg);
}
void show_alsa_error(const char *msg, int err)
{
const char *lines[2];
unsigned int count;
lines[0] = msg;
count = 1;
if (err < 0) {
lines[1] = snd_strerror(err);
count = 2;
}
create_text_box(lines, count, _("Error"), attrs.errormsg);
}
void show_textfile(const char *file_name)
{
char *buf;
unsigned int file_size;
unsigned int line_count;
unsigned int i;
const char **lines;
const char *start_line;
buf = read_file(file_name, &file_size);
if (!buf) {
int err = errno;
buf = casprintf(_("Cannot open file \"%s\"."), file_name);
show_error(buf, err);
return;
}
line_count = 0;
for (i = 0; i < file_size; ++i)
line_count += buf[i] == '\n';
lines = ccalloc(line_count, sizeof *lines);
line_count = 0;
start_line = buf;
for (i = 0; i < file_size; ++i) {
if (buf[i] == '\n') {
lines[line_count++] = start_line;
buf[i] = '\0';
start_line = &buf[i + 1];
}
if (buf[i] == '\t')
buf[i] = ' ';
}
create_text_box(lines, line_count, file_name, attrs.textbox);
free(lines);
free(buf);
}
void show_text(const char *const *lines, unsigned int count, const char *title)
{
create_text_box(lines, count, title, attrs.textbox);
}
/**********************************************************************/
static struct widget text_widget;
static char *title;
static int widget_attrs;
static char **text_lines;
static unsigned int text_lines_count;
static int max_line_width;
static int text_box_y;
static int text_box_x;
static int max_scroll_y;
static int max_scroll_x;
static int current_top;
static int current_left;
static void update_text_lines(void)
{
int i;
int width;
const char *line_begin;
const char *line_end;
int cur_y, cur_x;
int rest_of_line;
for (i = 0; i < text_box_y; ++i) {
width = current_left;
line_begin = mbs_at_width(text_lines[current_top + i], &width, 1);
wmove(text_widget.window, i + 1, 1);
if (width > current_left)
waddch(text_widget.window, ' ');
if (*line_begin != '\0') {
width = text_box_x - (width > current_left);
line_end = mbs_at_width(line_begin, &width, -1);
if (width)
waddnstr(text_widget.window, line_begin,
line_end - line_begin);
}
getyx(text_widget.window, cur_y, cur_x);
if (cur_y == i + 1) {
rest_of_line = text_box_x + 1 - cur_x;
if (rest_of_line > 0)
wprintw(text_widget.window, "%*s", rest_of_line, "");
}
}
}
static void update_y_scroll_bar(void)
{
int length;
int begin;
if (max_scroll_y <= 0 || text_lines_count == 0)
return;
length = text_box_y * text_box_y / text_lines_count;
if (length >= text_box_y)
return;
begin = current_top * (text_box_y - length) / max_scroll_y;
mvwvline(text_widget.window, 1, text_box_x + 1, ' ', text_box_y);
mvwvline(text_widget.window, begin + 1, text_box_x + 1, ACS_BOARD, length);
}
static void update_x_scroll_bar(void)
{
int length;
int begin;
if (max_scroll_x <= 0 || max_line_width <= 0)
return;
length = text_box_x * text_box_x / max_line_width;
if (length >= text_box_x)
return;
begin = current_left * (text_box_x - length) / max_scroll_x;
mvwhline(text_widget.window, text_box_y + 1, 1, ' ', text_box_x);
mvwhline(text_widget.window, text_box_y + 1, begin + 1, ACS_BOARD, length);
}
static void move_x(int delta)
{
int left;
left = current_left + delta;
if (left < 0)
left = 0;
else if (left > max_scroll_x)
left = max_scroll_x;
if (left != current_left) {
current_left = left;
update_text_lines();
update_x_scroll_bar();
}
}
static void move_y(int delta)
{
int top;
top = current_top + delta;
if (top < 0)
top = 0;
else if (top > max_scroll_y)
top = max_scroll_y;
if (top != current_top) {
current_top = top;
update_text_lines();
update_y_scroll_bar();
}
}
static void on_handle_key(int key)
{
if (key >= (int)ARRAY_SIZE(textbox_bindings))
return;
switch (textbox_bindings[key]) {
case CMD_TEXTBOX_CLOSE:
text_widget.close();
break;
case CMD_TEXTBOX_DOWN:
move_y(1);
break;
case CMD_TEXTBOX_UP:
move_y(-1);
break;
case CMD_TEXTBOX_LEFT:
move_x(-1);
break;
case CMD_TEXTBOX_RIGHT:
move_x(1);
break;
case CMD_TEXTBOX_PAGE_DOWN:
move_y(text_box_y);
break;
case CMD_TEXTBOX_PAGE_UP:
move_y(-text_box_y);
break;
case CMD_TEXTBOX_TOP:
move_x(-max_scroll_x);
break;
case CMD_TEXTBOX_BOTTOM:
move_x(max_scroll_x);
break;
case CMD_TEXTBOX_PAGE_RIGHT:
move_x(8);
break;
case CMD_TEXTBOX_PAGE_LEFT:
move_x(-8);
break;
}
}
static bool create(void)
{
int len, width;
if (screen_lines < 3 || screen_cols < 8) {
text_widget.close();
beep();
return FALSE;
}
width = max_line_width;
len = get_mbs_width(title) + 2;
if (width < len)
width = len;
text_box_y = text_lines_count;
if (text_box_y > screen_lines - 2)
text_box_y = screen_lines - 2;
max_scroll_y = text_lines_count - text_box_y;
text_box_x = width;
if (text_box_x > screen_cols - 2)
text_box_x = screen_cols - 2;
max_scroll_x = max_line_width - text_box_x;
widget_init(&text_widget, text_box_y + 2, text_box_x + 2,
SCREEN_CENTER, SCREEN_CENTER, widget_attrs, WIDGET_BORDER);
mvwprintw(text_widget.window, 0, (text_box_x + 2 - get_mbs_width(title) - 2) / 2, " %s ", title);
if (current_top > max_scroll_y)
current_top = max_scroll_y;
if (current_left > max_scroll_x)
current_left = max_scroll_x;
update_text_lines();
update_y_scroll_bar();
update_x_scroll_bar();
return TRUE;
}
static void on_window_size_changed(void)
{
create();
}
static void on_close(void)
{
unsigned int i;
for (i = 0; i < text_lines_count; ++i)
free(text_lines[i]);
free(text_lines);
widget_free(&text_widget);
}
static struct widget text_widget = {
.handle_key = on_handle_key,
.window_size_changed = on_window_size_changed,
.close = on_close,
};
static void create_text_box(const char *const *lines, unsigned int count,
const char *title_, int attrs)
{
unsigned int i;
text_lines = ccalloc(count, sizeof *text_lines);
for (i = 0; i < count; ++i)
text_lines[i] = cstrdup(lines[i]);
text_lines_count = count;
max_line_width = get_max_mbs_width(lines, count);
title = cstrdup(title_);
widget_attrs = attrs;
current_top = 0;
current_left = 0;
create();
}

10
alsamixer/textbox.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef TEXTBOX_H_INCLUDED
#define TEXTBOX_H_INCLUDED
void show_error(const char *msg, int err);
void show_alsa_error(const char *msg, int err);
void show_text(const char *const *text_lines, unsigned int count,
const char *title);
void show_textfile(const char *file_name);
#endif

148
alsamixer/utils.c Normal file
View file

@ -0,0 +1,148 @@
/*
* utils.c - multibyte-string helpers
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE
#endif
#include "aconfig.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <errno.h>
#include <stdio.h>
#include "utils.h"
#include "mem.h"
/*
* mbs_at_width - compute screen position in a string
*
* For displaying strings on the screen, we have to know how many character
* cells are occupied. This function calculates the position in a multibyte
* string that is at a desired position.
*
* Parameters:
* s: the string
* width: on input, the desired number of character cells; on output, the actual
* position, in character cells, of the return value
* dir: -1 or 1; in which direction to round if a multi-column character goes
* over the desired width
*
* Return value:
* Pointer to the place in the string that is as near the desired width as
* possible. If the string is too short, the return value points to the
* terminating zero. If the last character is a multi-column character that
* goes over the desired width, the return value may be one character cell
* earlier or later than desired, depending on the dir parameter.
* In any case, the return value points after any zero-width characters that
* follow the last character.
*/
const char *mbs_at_width(const char *s, int *width, int dir)
{
size_t len;
wchar_t wc;
int bytes;
int width_so_far, w;
if (*width <= 0)
return s;
mbtowc(NULL, NULL, 0); /* reset shift state */
len = strlen(s);
width_so_far = 0;
while (len && (bytes = mbtowc(&wc, s, len)) > 0) {
w = wcwidth(wc);
if (width_so_far + w > *width && dir < 0)
break;
if (w >= 0)
width_so_far += w;
s += bytes;
len -= bytes;
if (width_so_far >= *width) {
while (len && (bytes = mbtowc(&wc, s, len)) > 0) {
w = wcwidth(wc);
if (w != 0)
break;
s += bytes;
len -= bytes;
}
break;
}
}
*width = width_so_far;
return s;
}
/*
* get_mbs_width - compute screen width of a string
*/
unsigned int get_mbs_width(const char *s)
{
int width;
width = INT_MAX;
mbs_at_width(s, &width, 1);
return width;
}
/*
* get_max_mbs_width - get width of longest string in an array
*/
unsigned int get_max_mbs_width(const char *const *s, unsigned int count)
{
unsigned int max_width, i, len;
max_width = 0;
for (i = 0; i < count; ++i) {
len = get_mbs_width(s[i]);
if (len > max_width)
max_width = len;
}
return max_width;
}
#define MAX_FILE_SIZE 1048576
char *read_file(const char *file_name, unsigned int *file_size)
{
FILE *f;
int err;
char *buf;
unsigned int allocated = 2048;
unsigned int bytes_read;
f = fopen(file_name, "r");
if (!f) {
err = errno;
errno = err;
return NULL;
}
*file_size = 0;
buf = NULL;
do {
allocated *= 2;
buf = crealloc(buf, allocated);
bytes_read = fread(buf + *file_size, 1, allocated - *file_size, f);
*file_size += bytes_read;
} while (*file_size == allocated && allocated < MAX_FILE_SIZE);
fclose(f);
if (*file_size > 0 && buf[*file_size - 1] != '\n' && *file_size < allocated) {
buf[*file_size] = '\n';
++*file_size;
}
return buf;
}

11
alsamixer/utils.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef UTILS_H_INCLUDED
#define UTILS_H_INCLUDED
#define ARRAY_SIZE(a) (sizeof(a) / sizeof *(a))
unsigned int get_mbs_width(const char *s);
unsigned int get_max_mbs_width(const char *const *s, unsigned int count);
const char *mbs_at_width(const char *s, int *width, int dir);
char *read_file(const char *file_name, unsigned int *file_size);
#endif

181
alsamixer/volume_mapping.c Normal file
View file

@ -0,0 +1,181 @@
/*
* Copyright (c) 2010 Clemens Ladisch <clemens@ladisch.de>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* The functions in this file map the value ranges of ALSA mixer controls onto
* the interval 0..1.
*
* The mapping is designed so that the position in the interval is proportional
* to the volume as a human ear would perceive it (i.e., the position is the
* cubic root of the linear sample multiplication factor). For controls with
* a small range (24 dB or less), the mapping is linear in the dB values so
* that each step has the same size visually. Only for controls without dB
* information, a linear mapping of the hardware volume register values is used
* (this is the same algorithm as used in the old alsamixer).
*
* When setting the volume, 'dir' is the rounding direction:
* -1/0/1 = down/nearest/up.
*/
#define _ISOC99_SOURCE /* lrint() */
#include "aconfig.h"
#include <math.h>
#include <stdbool.h>
#include "volume_mapping.h"
#define MAX_LINEAR_DB_SCALE 24
static inline bool use_linear_dB_scale(long dBmin, long dBmax)
{
return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100;
}
static long lrint_dir(double x, int dir)
{
if (dir > 0)
return lrint(ceil(x));
else if (dir < 0)
return lrint(floor(x));
else
return lrint(x);
}
enum ctl_dir { PLAYBACK, CAPTURE };
static int (* const get_dB_range[2])(snd_mixer_elem_t *, long *, long *) = {
snd_mixer_selem_get_playback_dB_range,
snd_mixer_selem_get_capture_dB_range,
};
static int (* const get_raw_range[2])(snd_mixer_elem_t *, long *, long *) = {
snd_mixer_selem_get_playback_volume_range,
snd_mixer_selem_get_capture_volume_range,
};
static int (* const get_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
snd_mixer_selem_get_playback_dB,
snd_mixer_selem_get_capture_dB,
};
static int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
snd_mixer_selem_get_playback_volume,
snd_mixer_selem_get_capture_volume,
};
static int (* const set_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long, int) = {
snd_mixer_selem_set_playback_dB,
snd_mixer_selem_set_capture_dB,
};
static int (* const set_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long) = {
snd_mixer_selem_set_playback_volume,
snd_mixer_selem_set_capture_volume,
};
static double get_normalized_volume(snd_mixer_elem_t *elem,
snd_mixer_selem_channel_id_t channel,
enum ctl_dir ctl_dir)
{
long min, max, value;
double normalized, min_norm;
int err;
err = get_dB_range[ctl_dir](elem, &min, &max);
if (err < 0 || min >= max) {
err = get_raw_range[ctl_dir](elem, &min, &max);
if (err < 0 || min == max)
return 0;
err = get_raw[ctl_dir](elem, channel, &value);
if (err < 0)
return 0;
return (value - min) / (double)(max - min);
}
err = get_dB[ctl_dir](elem, channel, &value);
if (err < 0)
return 0;
if (use_linear_dB_scale(min, max))
return (value - min) / (double)(max - min);
normalized = pow(10, (value - max) / 6000.0);
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
min_norm = pow(10, (min - max) / 6000.0);
normalized = (normalized - min_norm) / (1 - min_norm);
}
return normalized;
}
static int set_normalized_volume(snd_mixer_elem_t *elem,
snd_mixer_selem_channel_id_t channel,
double volume,
int dir,
enum ctl_dir ctl_dir)
{
long min, max, value;
double min_norm;
int err;
err = get_dB_range[ctl_dir](elem, &min, &max);
if (err < 0 || min >= max) {
err = get_raw_range[ctl_dir](elem, &min, &max);
if (err < 0)
return err;
value = lrint_dir(volume * (max - min), dir) + min;
return set_raw[ctl_dir](elem, channel, value);
}
if (use_linear_dB_scale(min, max)) {
value = lrint_dir(volume * (max - min), dir) + min;
return set_dB[ctl_dir](elem, channel, value, dir);
}
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
min_norm = pow(10, (min - max) / 6000.0);
volume = volume * (1 - min_norm) + min_norm;
}
if (volume <= 0)
volume = 1e-36;
value = lrint_dir(6000.0 * log10(volume), dir) + max;
return set_dB[ctl_dir](elem, channel, value, dir);
}
double get_normalized_playback_volume(snd_mixer_elem_t *elem,
snd_mixer_selem_channel_id_t channel)
{
return get_normalized_volume(elem, channel, PLAYBACK);
}
double get_normalized_capture_volume(snd_mixer_elem_t *elem,
snd_mixer_selem_channel_id_t channel)
{
return get_normalized_volume(elem, channel, CAPTURE);
}
int set_normalized_playback_volume(snd_mixer_elem_t *elem,
snd_mixer_selem_channel_id_t channel,
double volume,
int dir)
{
return set_normalized_volume(elem, channel, volume, dir, PLAYBACK);
}
int set_normalized_capture_volume(snd_mixer_elem_t *elem,
snd_mixer_selem_channel_id_t channel,
double volume,
int dir)
{
return set_normalized_volume(elem, channel, volume, dir, CAPTURE);
}

View file

@ -0,0 +1,19 @@
#ifndef VOLUME_MAPPING_H_INCLUDED
#define VOLUME_MAPPING_H_INCLUDED
#include <alsa/asoundlib.h>
double get_normalized_playback_volume(snd_mixer_elem_t *elem,
snd_mixer_selem_channel_id_t channel);
double get_normalized_capture_volume(snd_mixer_elem_t *elem,
snd_mixer_selem_channel_id_t channel);
int set_normalized_playback_volume(snd_mixer_elem_t *elem,
snd_mixer_selem_channel_id_t channel,
double volume,
int dir);
int set_normalized_capture_volume(snd_mixer_elem_t *elem,
snd_mixer_selem_channel_id_t channel,
double volume,
int dir);
#endif

139
alsamixer/widget.c Normal file
View file

@ -0,0 +1,139 @@
/*
* widget.c - handles widget objects and the widget stack
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aconfig.h"
#include <stdlib.h>
#include <term.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "die.h"
#include "widget.h"
int screen_lines;
int screen_cols;
static int cursor_visibility = -1;
static void update_cursor_visibility(void)
{
const struct widget *active_widget;
active_widget = get_active_widget();
if (active_widget &&
active_widget->cursor_visibility != cursor_visibility) {
cursor_visibility = active_widget->cursor_visibility;
curs_set(cursor_visibility);
}
}
void widget_init(struct widget *widget, int lines_, int cols, int y, int x,
chtype bkgd, unsigned int flags)
{
WINDOW *old_window;
if (y == SCREEN_CENTER)
y = (screen_lines - lines_) / 2;
if (x == SCREEN_CENTER)
x = (screen_cols - cols) / 2;
old_window = widget->window;
widget->window = newwin(lines_, cols, y, x);
if (!widget->window)
fatal_error("cannot create window");
keypad(widget->window, TRUE);
nodelay(widget->window, TRUE);
leaveok(widget->window, !(flags & WIDGET_CURSOR_VISIBLE));
wbkgdset(widget->window, bkgd);
werase(widget->window);
if (flags & WIDGET_BORDER)
box(widget->window, 0, 0);
if (flags & WIDGET_SUBWINDOW) {
if (widget->subwindow)
delwin(widget->subwindow);
widget->subwindow = derwin(widget->window,
lines_ - 2, cols - 2, 1, 1);
if (!widget->subwindow)
fatal_error("cannot create subwindow");
wbkgdset(widget->subwindow, bkgd);
}
widget->cursor_visibility = !!(flags & WIDGET_CURSOR_VISIBLE);
if (widget->panel) {
replace_panel(widget->panel, widget->window);
} else {
widget->panel = new_panel(widget->window);
if (!widget->panel)
fatal_error("cannot create panel");
set_panel_userptr(widget->panel, widget);
}
if (old_window)
delwin(old_window);
update_cursor_visibility();
}
void widget_free(struct widget *widget)
{
if (widget->panel) {
del_panel(widget->panel);
widget->panel = NULL;
}
if (widget->subwindow) {
delwin(widget->subwindow);
widget->subwindow = NULL;
}
if (widget->window) {
delwin(widget->window);
widget->window = NULL;
}
update_cursor_visibility();
}
const struct widget *get_active_widget(void)
{
PANEL *active_panel;
active_panel = panel_below(NULL);
if (active_panel)
return panel_userptr(active_panel);
else
return NULL;
}
void window_size_changed(void)
{
PANEL *panel, *below;
const struct widget *widget;
struct winsize size;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == 0)
resize_term(size.ws_row, size.ws_col);
getmaxyx(stdscr, screen_lines, screen_cols);
if (tigetflag("xenl") != 1 && tigetflag("am") != 1)
--screen_lines;
for (panel = panel_below(NULL); panel; panel = below) {
below = panel_below(panel);
widget = panel_userptr(panel);
widget->window_size_changed();
}
}

33
alsamixer/widget.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef WIDGET_H_INCLUDED
#define WIDGET_H_INCLUDED
#include <panel.h>
#define WIDGET_BORDER 0x1
#define WIDGET_SUBWINDOW 0x2
#define WIDGET_CURSOR_VISIBLE 0x4
#define SCREEN_CENTER -1
struct widget {
WINDOW *window;
WINDOW *subwindow; /* optional: contents without border */
PANEL *panel;
int cursor_visibility;
void (*handle_key)(int key);
void (*window_size_changed)(void);
void (*close)(void);
};
extern int screen_lines;
extern int screen_cols;
void widget_init(struct widget *widget,
int lines_, int cols, int y, int x,
chtype bkgd, unsigned int flags);
void widget_free(struct widget *widget);
const struct widget *get_active_widget(void);
void window_size_changed(void);
#endif