android-16.0
This commit is contained in:
commit
d2fc7afa59
300 changed files with 84772 additions and 0 deletions
30
alsamixer/Makefile.am
Normal file
30
alsamixer/Makefile.am
Normal 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
400
alsamixer/alsamixer.1
Normal 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>.
|
||||
192
alsamixer/alsamixer.rc.example
Normal file
192
alsamixer/alsamixer.rc.example
Normal 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
132
alsamixer/bindings.c
Normal 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
81
alsamixer/bindings.h
Normal 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
201
alsamixer/card_select.c
Normal 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
7
alsamixer/card_select.h
Normal 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
180
alsamixer/cli.c
Normal 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
133
alsamixer/colors.c
Normal 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
42
alsamixer/colors.h
Normal 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
601
alsamixer/configparser.c
Normal 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
9
alsamixer/configparser.h
Normal 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
274
alsamixer/curskey.c
Normal 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
37
alsamixer/curskey.h
Normal 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
195
alsamixer/device_name.c
Normal 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
6
alsamixer/device_name.h
Normal 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
39
alsamixer/die.c
Normal 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
7
alsamixer/die.h
Normal 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
10
alsamixer/go
Executable 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
148
alsamixer/mainloop.c
Normal 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
10
alsamixer/mainloop.h
Normal 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
67
alsamixer/mem.c
Normal 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
11
alsamixer/mem.h
Normal 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
92
alsamixer/menu_widget.c
Normal 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
10
alsamixer/menu_widget.h
Normal 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
136
alsamixer/mixer_clickable.c
Normal 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;
|
||||
}
|
||||
21
alsamixer/mixer_clickable.h
Normal file
21
alsamixer/mixer_clickable.h
Normal 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
519
alsamixer/mixer_controls.c
Normal 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();
|
||||
}
|
||||
37
alsamixer/mixer_controls.h
Normal file
37
alsamixer/mixer_controls.h
Normal 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
763
alsamixer/mixer_display.c
Normal 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
10
alsamixer/mixer_display.h
Normal 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
674
alsamixer/mixer_widget.c
Normal 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(¤t_selem_id);
|
||||
if (err < 0)
|
||||
fatal_error("out of memory");
|
||||
}
|
||||
|
||||
static void set_view_mode(enum view_mode m)
|
||||
{
|
||||
view_mode = m;
|
||||
create_controls();
|
||||
}
|
||||
|
||||
static void close_hctl(void)
|
||||
{
|
||||
free_controls();
|
||||
if (mixer_device_name) {
|
||||
snd_mixer_detach(mixer, mixer_device_name);
|
||||
free(mixer_device_name);
|
||||
mixer_device_name = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void check_unplugged(void)
|
||||
{
|
||||
snd_hctl_t *hctl;
|
||||
snd_ctl_t *ctl;
|
||||
unsigned int state;
|
||||
int err;
|
||||
|
||||
unplugged = FALSE;
|
||||
if (mixer_device_name) {
|
||||
err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
|
||||
if (err >= 0) {
|
||||
ctl = snd_hctl_ctl(hctl);
|
||||
/* just any random function that does an ioctl() */
|
||||
err = snd_ctl_get_power_state(ctl, &state);
|
||||
if (err == -ENODEV)
|
||||
unplugged = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void close_mixer_device(void)
|
||||
{
|
||||
check_unplugged();
|
||||
close_hctl();
|
||||
|
||||
display_card_info();
|
||||
set_view_mode(view_mode);
|
||||
}
|
||||
|
||||
bool select_card_by_name(const char *device_name)
|
||||
{
|
||||
int err;
|
||||
bool opened;
|
||||
char *msg;
|
||||
|
||||
close_hctl();
|
||||
unplugged = FALSE;
|
||||
|
||||
opened = FALSE;
|
||||
if (device_name) {
|
||||
err = snd_mixer_attach(mixer, device_name);
|
||||
if (err >= 0)
|
||||
opened = TRUE;
|
||||
else {
|
||||
msg = casprintf(_("Cannot open mixer device '%s'."), device_name);
|
||||
show_alsa_error(msg, err);
|
||||
free(msg);
|
||||
}
|
||||
}
|
||||
if (opened) {
|
||||
mixer_device_name = cstrdup(device_name);
|
||||
|
||||
err = snd_mixer_load(mixer);
|
||||
if (err < 0)
|
||||
fatal_alsa_error(_("cannot load mixer controls"), err);
|
||||
}
|
||||
|
||||
display_card_info();
|
||||
set_view_mode(view_mode);
|
||||
return opened;
|
||||
}
|
||||
|
||||
static void show_help(void)
|
||||
{
|
||||
const char *help[] = {
|
||||
_("Esc Exit"),
|
||||
_("F1 ? H Help"),
|
||||
_("F2 / System information"),
|
||||
_("F3 Show playback controls"),
|
||||
_("F4 Show capture controls"),
|
||||
_("F5 Show all controls"),
|
||||
_("Tab Toggle view mode (F3/F4/F5)"),
|
||||
_("F6 S Select sound card"),
|
||||
_("L Redraw screen"),
|
||||
"",
|
||||
_("Left Move to the previous control"),
|
||||
_("Right Move to the next control"),
|
||||
"",
|
||||
_("Up/Down Change volume"),
|
||||
_("+ - Change volume"),
|
||||
_("Page Up/Dn Change volume in big steps"),
|
||||
_("End Set volume to 0%"),
|
||||
_("0-9 Set volume to 0%-90%"),
|
||||
_("Q W E Increase left/both/right volumes"),
|
||||
/* TRANSLATORS: or Y instead of Z */
|
||||
_("Z X C Decrease left/both/right volumes"),
|
||||
_("B Balance left and right volumes"),
|
||||
"",
|
||||
_("M Toggle mute"),
|
||||
/* TRANSLATORS: or , . */
|
||||
_("< > Toggle left/right mute"),
|
||||
"",
|
||||
_("Space Toggle capture"),
|
||||
/* TRANSLATORS: or Insert Delete */
|
||||
_("; ' Toggle left/right capture"),
|
||||
"",
|
||||
_("Authors:"),
|
||||
_(" Tim Janik"),
|
||||
_(" 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
45
alsamixer/mixer_widget.h
Normal 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
107
alsamixer/proc_files.c
Normal 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
6
alsamixer/proc_files.h
Normal 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
335
alsamixer/textbox.c
Normal 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
10
alsamixer/textbox.h
Normal 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
148
alsamixer/utils.c
Normal 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
11
alsamixer/utils.h
Normal 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
181
alsamixer/volume_mapping.c
Normal 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);
|
||||
}
|
||||
19
alsamixer/volume_mapping.h
Normal file
19
alsamixer/volume_mapping.h
Normal 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
139
alsamixer/widget.c
Normal 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
33
alsamixer/widget.h
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue