mirror of https://github.com/ericonr/sndio.git
move the mixer api to a dedicated "mixer" branch
This commit is contained in:
parent
e8007feb00
commit
1e958d6853
|
@ -1,53 +0,0 @@
|
||||||
# extra includes paths (-I options)
|
|
||||||
INCLUDE = -I../libsndio -I../bsd-compat
|
|
||||||
|
|
||||||
# extra libraries paths (-L options)
|
|
||||||
LIB = -L../libsndio
|
|
||||||
|
|
||||||
# extra defines (-D options)
|
|
||||||
DEFS = -DDEBUG @defs@
|
|
||||||
|
|
||||||
# extra libraries (-l options)
|
|
||||||
LDADD = -lsndio @ldadd@
|
|
||||||
|
|
||||||
# variables defined on configure script command line (if any)
|
|
||||||
@vars@
|
|
||||||
|
|
||||||
#
|
|
||||||
# binaries, documentation, man pages and examples will be installed in
|
|
||||||
# ${BIN_DIR}, ${MAN1_DIR}
|
|
||||||
#
|
|
||||||
BIN_DIR = @bindir@
|
|
||||||
MAN1_DIR = @mandir@/man1
|
|
||||||
|
|
||||||
#
|
|
||||||
# programs to build
|
|
||||||
#
|
|
||||||
PROG = sndioctl
|
|
||||||
MAN1 = sndioctl.1
|
|
||||||
|
|
||||||
all: ${PROG} ${MAN1}
|
|
||||||
|
|
||||||
install:
|
|
||||||
mkdir -p ${DESTDIR}${BIN_DIR} ${DESTDIR}${MAN1_DIR}
|
|
||||||
cp sndioctl ${DESTDIR}${BIN_DIR}
|
|
||||||
cp sndioctl.1 ${DESTDIR}${MAN1_DIR}
|
|
||||||
|
|
||||||
uninstall:
|
|
||||||
cd ${DESTDIR}${BIN_DIR} && rm -f ${PROG}
|
|
||||||
cd ${DESTDIR}${MAN1_DIR} && rm -f ${MAN1}
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f -- *.o sndioctl
|
|
||||||
|
|
||||||
# ---------------------------------------------------------- dependencies ---
|
|
||||||
|
|
||||||
OBJS = sndioctl.o
|
|
||||||
|
|
||||||
sndioctl: ${OBJS}
|
|
||||||
${CC} ${LDFLAGS} ${LIB} -o sndioctl ${OBJS} ${LDADD}
|
|
||||||
|
|
||||||
.c.o:
|
|
||||||
${CC} ${CFLAGS} ${INCLUDE} ${DEFS} -c $<
|
|
||||||
|
|
||||||
sndioctl.o: sndioctl.c
|
|
|
@ -1,156 +0,0 @@
|
||||||
.\" $OpenBSD$
|
|
||||||
.\"
|
|
||||||
.\" Copyright (c) 2007 Alexandre Ratchov <alex@caoua.org>
|
|
||||||
.\"
|
|
||||||
.\" Permission to use, copy, modify, and 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.
|
|
||||||
.\"
|
|
||||||
.Dd $Mdocdate: April 8 2011 $
|
|
||||||
.Dt SNDIOCTL 1
|
|
||||||
.Os
|
|
||||||
.Sh NAME
|
|
||||||
.Nm sndioctl
|
|
||||||
.Nd control audio mixer parameters
|
|
||||||
.Sh SYNOPSIS
|
|
||||||
.Nm
|
|
||||||
.Bk -words
|
|
||||||
.Op Fl iv
|
|
||||||
.Op Fl f Ar device
|
|
||||||
.Op Ar command ...
|
|
||||||
.Ek
|
|
||||||
.Nm
|
|
||||||
.Bk -words
|
|
||||||
.Fl d
|
|
||||||
.Ek
|
|
||||||
.Sh DESCRIPTION
|
|
||||||
The
|
|
||||||
.Nm
|
|
||||||
utility can display or change mixer parameters of
|
|
||||||
.Xr sndio 7
|
|
||||||
audio devices.
|
|
||||||
The options are as follows:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Fl d
|
|
||||||
Dump the raw list of available parameters and exit.
|
|
||||||
Useful to debug device drivers or programs using the
|
|
||||||
.Xr siomix_open 3
|
|
||||||
interface.
|
|
||||||
.It Fl f Ar device
|
|
||||||
Use this
|
|
||||||
.Xr sndio 7
|
|
||||||
mixer device.
|
|
||||||
.It Fl m
|
|
||||||
Monitor and display mixer changes.
|
|
||||||
.It Fl n
|
|
||||||
Don't hide program names associated to audio streams (by
|
|
||||||
default they are displayed as comments).
|
|
||||||
.It Fl i
|
|
||||||
Display characteristics of requested parameters
|
|
||||||
instead of their values.
|
|
||||||
.It Fl v
|
|
||||||
Enable verbose mode, a.k.a. multi-channel mode.
|
|
||||||
By default parameters affecting different channels
|
|
||||||
of the same stream are disguised as a single mono
|
|
||||||
parameter to hide details that are not essential.
|
|
||||||
.El
|
|
||||||
.Pp
|
|
||||||
If no commands are specified all valid parameters are displayed on
|
|
||||||
.Em stdout .
|
|
||||||
Unless
|
|
||||||
.Fl d
|
|
||||||
or
|
|
||||||
.Fl i
|
|
||||||
are used, displayed parameters are valid commands.
|
|
||||||
The set of available controls depends on the mixer device.
|
|
||||||
.Pp
|
|
||||||
Commands use the following two formats to display and set
|
|
||||||
parameters respectively:
|
|
||||||
.Pp
|
|
||||||
.Dl <substream>.<parameter>
|
|
||||||
.Dl <substream>.<parameter>=<value>
|
|
||||||
.Pp
|
|
||||||
On the left-hand side, the affected substream is specified
|
|
||||||
by the stream name followed by an optional
|
|
||||||
channel range.
|
|
||||||
Examples of left-hand side terms:
|
|
||||||
.Pp
|
|
||||||
.Dl spkr.level
|
|
||||||
.Dl spkr[2-5].level
|
|
||||||
.Dl spkr[6].level
|
|
||||||
.Pp
|
|
||||||
There are three parameter types: numbers, selectors and vectors.
|
|
||||||
.Pp
|
|
||||||
Numbers are specified in decimal and follow the same semantics
|
|
||||||
as MIDI controllers.
|
|
||||||
Values are in the 0..127 range and 64 is the neutral state (if applicable).
|
|
||||||
Two-state controls (switches) take any value in the 0..63 range or
|
|
||||||
in the 64..127 range typically to the
|
|
||||||
.Em off
|
|
||||||
and
|
|
||||||
.Em on
|
|
||||||
states respectively.
|
|
||||||
.Pp
|
|
||||||
If a decimal is prefixed by the plus (minus) sign then
|
|
||||||
the given value is added to (subtracted from) the
|
|
||||||
current value of the control.
|
|
||||||
If
|
|
||||||
.Qq \&!
|
|
||||||
is used instead of a number, then the switch is toggled.
|
|
||||||
Examples:
|
|
||||||
.Pp
|
|
||||||
.Dl spkr.level=85
|
|
||||||
.Dl spkr[4-5].level=+10
|
|
||||||
.Dl spkr.mute=0
|
|
||||||
.Dl spkr.mute=!
|
|
||||||
.Pp
|
|
||||||
.Pp Selectors
|
|
||||||
Selector values are substreams; they are specified
|
|
||||||
as the stream name followed by an optional channel
|
|
||||||
range.
|
|
||||||
If no channel range is specified, the same
|
|
||||||
range as the stream specified on the left-hand side is used.
|
|
||||||
For instance the following are equivalent:
|
|
||||||
.Pp
|
|
||||||
.Dl record[1].source=mic
|
|
||||||
.Dl record[1].source=mic[1]
|
|
||||||
.Pp
|
|
||||||
.Pp Vectors
|
|
||||||
Vectors are arrays of numbers.
|
|
||||||
Values are specified as comma-separated components.
|
|
||||||
Each component is a substream, followed by
|
|
||||||
a colon, followed by a number.
|
|
||||||
If the colon and the number are omitted, then 127 is
|
|
||||||
assumed.
|
|
||||||
If a component is missing, then 0 is assumed.
|
|
||||||
Example:
|
|
||||||
.Pp
|
|
||||||
.Dl monitor.mix=play:120,linein:85
|
|
||||||
.Dl record.source=mic,linein
|
|
||||||
.Pp
|
|
||||||
Numbers are specified as discussed above.
|
|
||||||
Note that a vector of switches is equivalent to
|
|
||||||
a list.
|
|
||||||
.Sh SEE ALSO
|
|
||||||
.Xr siomix_open 3
|
|
||||||
.Sh EXAMPLES
|
|
||||||
The following will set all
|
|
||||||
.Ar level
|
|
||||||
parameters that control the
|
|
||||||
.Ar spkr
|
|
||||||
stream to zero.
|
|
||||||
.Pp
|
|
||||||
.Dl $ sndioctl spkr.level=0
|
|
||||||
.Pp
|
|
||||||
The following commands are equivalent:
|
|
||||||
.Pp
|
|
||||||
.Dl $ sndioctl record[0].source=mic[0] record[1].source=mic[1]
|
|
||||||
.Dl $ sndioctl record.source=mic
|
|
|
@ -1,926 +0,0 @@
|
||||||
/* $OpenBSD$ */
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2007-2011 Alexandre Ratchov <alex@caoua.org>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and 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.
|
|
||||||
*/
|
|
||||||
#include <errno.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sndio.h>
|
|
||||||
|
|
||||||
#define IS_IDENT(c) (((c) >= 'a' && (c) <= 'z') || \
|
|
||||||
((c) >= 'A' && (c) <= 'Z') || \
|
|
||||||
((c) >= '0' && (c) <= '9') || \
|
|
||||||
((c) == '_'))
|
|
||||||
|
|
||||||
struct info {
|
|
||||||
struct info *next;
|
|
||||||
struct siomix_desc desc;
|
|
||||||
unsigned ctladdr;
|
|
||||||
#define MODE_IGNORE 0 /* ignore this value */
|
|
||||||
#define MODE_PRINT 1 /* print-only, don't change value */
|
|
||||||
#define MODE_SET 2 /* set to newval value */
|
|
||||||
#define MODE_ADD 3 /* increase current value by newval */
|
|
||||||
#define MODE_SUB 4 /* decrease current value by newval */
|
|
||||||
#define MODE_TOGGLE 5 /* toggle current value */
|
|
||||||
unsigned mode;
|
|
||||||
int curval, newval;
|
|
||||||
};
|
|
||||||
|
|
||||||
int cmpdesc(struct siomix_desc *, struct siomix_desc *);
|
|
||||||
int isdiag(struct info *);
|
|
||||||
struct info *selpos(struct info *);
|
|
||||||
struct info *vecent(struct info *, char *, char *);
|
|
||||||
struct info *nextgrp(struct info *);
|
|
||||||
struct info *nextpar(struct info *);
|
|
||||||
struct info *firstent(struct info *, char *);
|
|
||||||
struct info *nextent(struct info *, int);
|
|
||||||
int matchpar(struct info *, char *, char *);
|
|
||||||
int matchent(struct info *, char *, char *);
|
|
||||||
int ismono(struct info *);
|
|
||||||
void print_chan(struct siomix_chan *, int);
|
|
||||||
void print_desc(struct info *, int);
|
|
||||||
void print_val(struct info *, int);
|
|
||||||
void print_par(struct info *, int);
|
|
||||||
int parse_name(char **, char *);
|
|
||||||
int parse_dec(char **, unsigned *);
|
|
||||||
int parse_chan(char **, char *, char *);
|
|
||||||
int parse_modeval(char **, int *, unsigned *);
|
|
||||||
void dump(void);
|
|
||||||
int cmd(char *);
|
|
||||||
void commit(void);
|
|
||||||
void list(void);
|
|
||||||
void ondesc(void *, struct siomix_desc *, int);
|
|
||||||
void onctl(void *, unsigned, unsigned);
|
|
||||||
|
|
||||||
struct siomix_hdl *hdl;
|
|
||||||
struct info *infolist;
|
|
||||||
int n_flag = 0, i_flag = 0, v_flag = 0, m_flag = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* compare two siomix_desc structures, used to sort infolist
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
cmpdesc(struct siomix_desc *d1, struct siomix_desc *d2)
|
|
||||||
{
|
|
||||||
int res;
|
|
||||||
|
|
||||||
res = strcmp(d1->chan0.str, d2->chan0.str);
|
|
||||||
if (res != 0)
|
|
||||||
return res;
|
|
||||||
res = d1->type - d2->type;
|
|
||||||
if (res != 0)
|
|
||||||
return res;
|
|
||||||
res = strcmp(d1->func, d2->func);
|
|
||||||
if (res != 0)
|
|
||||||
return res;
|
|
||||||
res = strcmp(d1->chan0.opt, d2->chan0.opt);
|
|
||||||
if (d1->type == SIOMIX_VEC ||
|
|
||||||
d1->type == SIOMIX_LIST) {
|
|
||||||
if (res != 0)
|
|
||||||
return res;
|
|
||||||
res = strcmp(d1->chan1.str, d2->chan1.str);
|
|
||||||
if (res != 0)
|
|
||||||
return res;
|
|
||||||
res = strcmp(d1->chan1.opt, d2->chan1.opt);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* return true of the selector or vector entry is diagonal
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
isdiag(struct info *e)
|
|
||||||
{
|
|
||||||
if (strlen(e->desc.chan0.opt) == 0 ||
|
|
||||||
strlen(e->desc.chan1.opt) == 0)
|
|
||||||
return 1;
|
|
||||||
return strcmp(e->desc.chan1.opt, e->desc.chan0.opt) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* find the value of the given selector parameter
|
|
||||||
*/
|
|
||||||
struct info *
|
|
||||||
selpos(struct info *i)
|
|
||||||
{
|
|
||||||
while (i != NULL) {
|
|
||||||
if (i->curval)
|
|
||||||
return i;
|
|
||||||
i = i->next;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "selpos: not found, bogus mixer\n");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* find the selector or vector entry with the given name and channels
|
|
||||||
*/
|
|
||||||
struct info *
|
|
||||||
vecent(struct info *i, char *vstr, char *vopt)
|
|
||||||
{
|
|
||||||
while (i != NULL) {
|
|
||||||
if ((strcmp(i->desc.chan1.str, vstr) == 0) &&
|
|
||||||
(strlen(vopt) == 0 || strcmp(i->desc.chan1.opt, vopt) == 0))
|
|
||||||
break;
|
|
||||||
i = i->next;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* find the next parameter group
|
|
||||||
*/
|
|
||||||
struct info *
|
|
||||||
nextgrp(struct info *i)
|
|
||||||
{
|
|
||||||
char *str, *func;
|
|
||||||
|
|
||||||
func = i->desc.func;
|
|
||||||
str = i->desc.chan0.str;
|
|
||||||
for (i = i->next; i != NULL; i = i->next) {
|
|
||||||
if (strcmp(i->desc.chan0.str, str) != 0 ||
|
|
||||||
strcmp(i->desc.func, func) != 0)
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* find the next parameter of the same group
|
|
||||||
*/
|
|
||||||
struct info *
|
|
||||||
nextpar(struct info *i)
|
|
||||||
{
|
|
||||||
char *str, *opt, *func;
|
|
||||||
|
|
||||||
func = i->desc.func;
|
|
||||||
str = i->desc.chan0.str;
|
|
||||||
opt = i->desc.chan0.opt;
|
|
||||||
for (i = i->next; i != NULL; i = i->next) {
|
|
||||||
if (strcmp(i->desc.chan0.str, str) != 0 ||
|
|
||||||
strcmp(i->desc.func, func) != 0)
|
|
||||||
break;
|
|
||||||
if (strcmp(i->desc.chan0.opt, opt) != 0)
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* return the first structure having of a selector or vector group
|
|
||||||
*/
|
|
||||||
struct info *
|
|
||||||
firstent(struct info *g, char *vstr)
|
|
||||||
{
|
|
||||||
char *astr, *func;
|
|
||||||
struct info *i;
|
|
||||||
|
|
||||||
astr = g->desc.chan0.str;
|
|
||||||
func = g->desc.func;
|
|
||||||
for (i = g; i != NULL; i = i->next) {
|
|
||||||
if (strcmp(i->desc.chan0.str, astr) != 0 ||
|
|
||||||
strcmp(i->desc.func, func) != 0)
|
|
||||||
break;
|
|
||||||
if (!isdiag(i))
|
|
||||||
continue;
|
|
||||||
if (strcmp(i->desc.chan1.str, vstr) == 0)
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* find the next entry of the given selector or vector, if the mono flag
|
|
||||||
* is set then the whole group is searched and off-diagonal entries are
|
|
||||||
* skipped
|
|
||||||
*/
|
|
||||||
struct info *
|
|
||||||
nextent(struct info *i, int mono)
|
|
||||||
{
|
|
||||||
char *str, *opt, *func;
|
|
||||||
|
|
||||||
func = i->desc.func;
|
|
||||||
str = i->desc.chan0.str;
|
|
||||||
opt = i->desc.chan0.opt;
|
|
||||||
for (i = i->next; i != NULL; i = i->next) {
|
|
||||||
if (strcmp(i->desc.chan0.str, str) != 0 ||
|
|
||||||
strcmp(i->desc.func, func) != 0)
|
|
||||||
return NULL;
|
|
||||||
if (mono)
|
|
||||||
return i;
|
|
||||||
if (strcmp(i->desc.chan0.opt, opt) == 0)
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* return true if parameter matches the given name and channel range
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
matchpar(struct info *i, char *astr, char *aopt)
|
|
||||||
{
|
|
||||||
if (strcmp(i->desc.chan0.str, astr) != 0)
|
|
||||||
return 0;
|
|
||||||
if (strlen(aopt) == 0)
|
|
||||||
return 1;
|
|
||||||
else if (strlen(i->desc.chan0.opt) == 0) {
|
|
||||||
fprintf(stderr, "opt used for parameter with no opt\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
return strcmp(i->desc.chan0.opt, aopt) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* return true if selector or vector entry matches the given name and
|
|
||||||
* channel range
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
matchent(struct info *i, char *vstr, char *vopt)
|
|
||||||
{
|
|
||||||
if (strcmp(i->desc.chan1.str, vstr) != 0)
|
|
||||||
return 0;
|
|
||||||
if (strlen(vopt) == 0)
|
|
||||||
return 1;
|
|
||||||
else if (strlen(i->desc.chan1.opt) == 0) {
|
|
||||||
fprintf(stderr, "opt used for parameter with no opt\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
return strcmp(i->desc.chan1.opt, vopt) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* return true if the given group can be represented as a signle mono
|
|
||||||
* parameter
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
ismono(struct info *g)
|
|
||||||
{
|
|
||||||
struct info *p1, *p2;
|
|
||||||
struct info *e1, *e2;
|
|
||||||
|
|
||||||
p1 = g;
|
|
||||||
switch (g->desc.type) {
|
|
||||||
case SIOMIX_LABEL:
|
|
||||||
break;
|
|
||||||
case SIOMIX_NUM:
|
|
||||||
case SIOMIX_SW:
|
|
||||||
for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
|
|
||||||
if (p2->curval != p1->curval)
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SIOMIX_VEC:
|
|
||||||
case SIOMIX_LIST:
|
|
||||||
for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
|
|
||||||
for (e2 = p2; e2 != NULL; e2 = nextent(e2, 0)) {
|
|
||||||
if (!isdiag(e2)) {
|
|
||||||
if (e2->curval != 0)
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
e1 = vecent(p1,
|
|
||||||
e2->desc.chan1.str,
|
|
||||||
p1->desc.chan0.opt);
|
|
||||||
if (e1 == NULL)
|
|
||||||
continue;
|
|
||||||
if (e1->curval != e2->curval)
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* print a sub-stream, eg. "spkr[4-7]"
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
print_chan(struct siomix_chan *c, int mono)
|
|
||||||
{
|
|
||||||
printf("%s", c->str);
|
|
||||||
if (!mono && strlen(c->opt) > 0) {
|
|
||||||
printf("[%s]", c->opt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* print info about the parameter
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
print_desc(struct info *p, int mono)
|
|
||||||
{
|
|
||||||
struct info *e;
|
|
||||||
int more;
|
|
||||||
|
|
||||||
switch (p->desc.type) {
|
|
||||||
case SIOMIX_NUM:
|
|
||||||
case SIOMIX_SW:
|
|
||||||
printf("*");
|
|
||||||
break;
|
|
||||||
case SIOMIX_VEC:
|
|
||||||
case SIOMIX_LIST:
|
|
||||||
more = 0;
|
|
||||||
for (e = p; e != NULL; e = nextent(e, mono)) {
|
|
||||||
if (mono) {
|
|
||||||
if (!isdiag(e))
|
|
||||||
continue;
|
|
||||||
if (e != firstent(p, e->desc.chan1.str))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (more)
|
|
||||||
printf(",");
|
|
||||||
print_chan(&e->desc.chan1, mono);
|
|
||||||
printf(":*");
|
|
||||||
more = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* print parameter value
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
print_val(struct info *p, int mono)
|
|
||||||
{
|
|
||||||
struct info *e;
|
|
||||||
int more;
|
|
||||||
|
|
||||||
switch (p->desc.type) {
|
|
||||||
case SIOMIX_LABEL:
|
|
||||||
printf("%s", p->desc.chan1.str);
|
|
||||||
//print_chan(&e->desc.chan1, mono);
|
|
||||||
break;
|
|
||||||
case SIOMIX_NUM:
|
|
||||||
case SIOMIX_SW:
|
|
||||||
printf("%u", p->curval);
|
|
||||||
break;
|
|
||||||
case SIOMIX_VEC:
|
|
||||||
case SIOMIX_LIST:
|
|
||||||
more = 0;
|
|
||||||
for (e = p; e != NULL; e = nextent(e, mono)) {
|
|
||||||
if (mono) {
|
|
||||||
if (!isdiag(e))
|
|
||||||
continue;
|
|
||||||
if (e != firstent(p, e->desc.chan1.str))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (more)
|
|
||||||
printf(",");
|
|
||||||
print_chan(&e->desc.chan1, mono);
|
|
||||||
printf(":%u", e->curval);
|
|
||||||
more = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* print ``<parameter>=<value>'' string (including '\n')
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
print_par(struct info *p, int mono)
|
|
||||||
{
|
|
||||||
struct info *i;
|
|
||||||
int more;
|
|
||||||
|
|
||||||
print_chan(&p->desc.chan0, mono);
|
|
||||||
printf(".%s=", p->desc.func);
|
|
||||||
if (i_flag)
|
|
||||||
print_desc(p, mono);
|
|
||||||
else
|
|
||||||
print_val(p, mono);
|
|
||||||
|
|
||||||
/* append a comment with the labels (if any) */
|
|
||||||
if (!n_flag && p->desc.type != SIOMIX_LABEL) {
|
|
||||||
more = 0;
|
|
||||||
for (i = infolist; i != NULL; i = i->next) {
|
|
||||||
if (i->desc.type != SIOMIX_LABEL)
|
|
||||||
continue;
|
|
||||||
if (strcmp(i->desc.chan0.str, p->desc.chan0.str) == 0 &&
|
|
||||||
strcmp(i->desc.chan0.opt, p->desc.chan0.opt) == 0) {
|
|
||||||
if (!more) {
|
|
||||||
printf("\t#");
|
|
||||||
more = 1;
|
|
||||||
}
|
|
||||||
printf(" %s=%s", i->desc.func, i->desc.chan1.str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* parse a stream name or parameter name
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
parse_name(char **line, char *name)
|
|
||||||
{
|
|
||||||
char *p = *line;
|
|
||||||
unsigned len = 0;
|
|
||||||
|
|
||||||
if (!IS_IDENT(*p)) {
|
|
||||||
fprintf(stderr, "letter/digit expected near '%s'\n", p);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
while (IS_IDENT(*p)) {
|
|
||||||
if (len >= SIOMIX_NAMEMAX - 1) {
|
|
||||||
name[SIOMIX_NAMEMAX - 1] = '\0';
|
|
||||||
fprintf(stderr, "%s...: too long\n", name);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
name[len++] = *p;
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
name[len] = '\0';
|
|
||||||
*line = p;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* parse a decimal number
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
parse_dec(char **line, unsigned *num)
|
|
||||||
{
|
|
||||||
char *p = *line;
|
|
||||||
unsigned val = 0;
|
|
||||||
|
|
||||||
if (*p < '0' || *p > '9') {
|
|
||||||
fprintf(stderr, "number expected near '%s'\n", p);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
while (*p >= '0' && *p <= '9') {
|
|
||||||
val = 10 * val + (*p - '0');
|
|
||||||
if (val > SIOMIX_INTMAX) {
|
|
||||||
fprintf(stderr, "integer too large\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
*num = val;
|
|
||||||
*line = p;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* parse a sub-stream, eg. "spkr[4-7]"
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
parse_chan(char **line, char *str, char *opt)
|
|
||||||
{
|
|
||||||
char *p = *line;
|
|
||||||
|
|
||||||
if (!parse_name(&p, str))
|
|
||||||
return 0;
|
|
||||||
if (*p != '[') {
|
|
||||||
*opt = 0;
|
|
||||||
*line = p;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
if (!parse_name(&p, opt))
|
|
||||||
return 0;
|
|
||||||
if (*p != ']') {
|
|
||||||
fprintf(stderr, "']' expected near '%s'\n", p);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
*line = p;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* parse a decimal prefixed by the optional mode
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
parse_modeval(char **line, int *rmode, unsigned *rval)
|
|
||||||
{
|
|
||||||
char *p = *line;
|
|
||||||
unsigned mode;
|
|
||||||
|
|
||||||
switch (*p) {
|
|
||||||
case '+':
|
|
||||||
mode = MODE_ADD;
|
|
||||||
p++;
|
|
||||||
break;
|
|
||||||
case '-':
|
|
||||||
mode = MODE_SUB;
|
|
||||||
p++;
|
|
||||||
break;
|
|
||||||
case '!':
|
|
||||||
mode = MODE_TOGGLE;
|
|
||||||
p++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mode = MODE_SET;
|
|
||||||
}
|
|
||||||
if (mode != MODE_TOGGLE) {
|
|
||||||
if (!parse_dec(&p, rval))
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
*line = p;
|
|
||||||
*rmode = mode;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* dump the whole mixer, useful for debugging
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
dump(void)
|
|
||||||
{
|
|
||||||
struct info *i;
|
|
||||||
|
|
||||||
for (i = infolist; i != NULL; i = i->next) {
|
|
||||||
printf("%03u:", i->ctladdr);
|
|
||||||
print_chan(&i->desc.chan0, 0);
|
|
||||||
printf(".%s", i->desc.func);
|
|
||||||
printf("=");
|
|
||||||
switch (i->desc.type) {
|
|
||||||
case SIOMIX_LABEL:
|
|
||||||
print_chan(&i->desc.chan1, 0);
|
|
||||||
break;
|
|
||||||
case SIOMIX_NUM:
|
|
||||||
case SIOMIX_SW:
|
|
||||||
printf("* (%u)", i->curval);
|
|
||||||
break;
|
|
||||||
case SIOMIX_VEC:
|
|
||||||
case SIOMIX_LIST:
|
|
||||||
print_chan(&i->desc.chan1, 0);
|
|
||||||
printf(":* (%u)", i->curval);
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* parse and execute a command ``<parameter>[=<value>]''
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
cmd(char *line)
|
|
||||||
{
|
|
||||||
char *pos = line;
|
|
||||||
struct info *i, *e, *g;
|
|
||||||
char func[SIOMIX_NAMEMAX], astr[SIOMIX_NAMEMAX], vstr[SIOMIX_NAMEMAX];
|
|
||||||
char aopt[SIOMIX_NAMEMAX], vopt[SIOMIX_NAMEMAX];
|
|
||||||
unsigned val, npar = 0, nent = 0;
|
|
||||||
int comma, mode;
|
|
||||||
|
|
||||||
if (!parse_chan(&pos, astr, aopt))
|
|
||||||
return 0;
|
|
||||||
if (*pos != '.') {
|
|
||||||
fprintf(stderr, "'.' expected near '%s'\n", pos);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
if (!parse_name(&pos, func))
|
|
||||||
return 0;
|
|
||||||
for (g = infolist;; g = g->next) {
|
|
||||||
if (g == NULL) {
|
|
||||||
fprintf(stderr, "%s.%s: no such group\n", astr, func);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (strcmp(g->desc.func, func) == 0 &&
|
|
||||||
strcmp(g->desc.chan0.str, astr) == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
g->mode = MODE_PRINT;
|
|
||||||
if (*pos != '=') {
|
|
||||||
if (*pos != '\0') {
|
|
||||||
fprintf(stderr, "junk at end of command\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
if (i_flag) {
|
|
||||||
printf("can't set values in info mode\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
npar = 0;
|
|
||||||
switch (g->desc.type) {
|
|
||||||
case SIOMIX_NUM:
|
|
||||||
case SIOMIX_SW:
|
|
||||||
if (!parse_modeval(&pos, &mode, &val))
|
|
||||||
return 0;
|
|
||||||
for (i = g; i != NULL; i = nextpar(i)) {
|
|
||||||
if (!matchpar(i, astr, aopt))
|
|
||||||
continue;
|
|
||||||
i->mode = mode;
|
|
||||||
i->newval = val;
|
|
||||||
npar++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SIOMIX_VEC:
|
|
||||||
case SIOMIX_LIST:
|
|
||||||
for (i = g; i != NULL; i = nextpar(i)) {
|
|
||||||
if (!matchpar(i, astr, aopt))
|
|
||||||
continue;
|
|
||||||
for (e = i; e != NULL; e = nextent(e, 0)) {
|
|
||||||
e->newval = 0;
|
|
||||||
e->mode = MODE_SET;
|
|
||||||
}
|
|
||||||
npar++;
|
|
||||||
}
|
|
||||||
comma = 0;
|
|
||||||
for (;;) {
|
|
||||||
if (*pos == '\0')
|
|
||||||
break;
|
|
||||||
if (comma) {
|
|
||||||
if (*pos != ',')
|
|
||||||
break;
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
if (!parse_chan(&pos, vstr, vopt))
|
|
||||||
return 0;
|
|
||||||
if (*pos == ':') {
|
|
||||||
pos++;
|
|
||||||
if (!parse_modeval(&pos, &mode, &val))
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
val = SIOMIX_INTMAX;
|
|
||||||
mode = MODE_SET;
|
|
||||||
}
|
|
||||||
nent = 0;
|
|
||||||
for (i = g; i != NULL; i = nextpar(i)) {
|
|
||||||
if (!matchpar(i, astr, aopt))
|
|
||||||
continue;
|
|
||||||
for (e = i; e != NULL; e = nextent(e, 0)) {
|
|
||||||
if (matchent(e, vstr, vopt)) {
|
|
||||||
e->newval = val;
|
|
||||||
e->mode = mode;
|
|
||||||
nent++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nent == 0) {
|
|
||||||
fprintf(stderr, "%s[%s]: invalid value\n", vstr, vopt);
|
|
||||||
print_par(g, 0);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
comma = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (npar == 0) {
|
|
||||||
fprintf(stderr, "%s: invalid parameter\n", line);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if (*pos != '\0') {
|
|
||||||
printf("%s: junk at end of command\n", pos);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* write on the mixer device entries with the ``set'' flag
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
commit(void)
|
|
||||||
{
|
|
||||||
struct info *i;
|
|
||||||
int val;
|
|
||||||
|
|
||||||
for (i = infolist; i != NULL; i = i->next) {
|
|
||||||
val = 0xdeadbeef;
|
|
||||||
switch (i->mode) {
|
|
||||||
case MODE_IGNORE:
|
|
||||||
case MODE_PRINT:
|
|
||||||
continue;
|
|
||||||
case MODE_SET:
|
|
||||||
val = i->newval;
|
|
||||||
break;
|
|
||||||
case MODE_ADD:
|
|
||||||
val = i->curval + i->newval;
|
|
||||||
if (val > SIOMIX_INTMAX)
|
|
||||||
val = SIOMIX_INTMAX;
|
|
||||||
break;
|
|
||||||
case MODE_SUB:
|
|
||||||
val = i->curval - i->newval;
|
|
||||||
if (val < 0)
|
|
||||||
val = 0;
|
|
||||||
break;
|
|
||||||
case MODE_TOGGLE:
|
|
||||||
val = (i->curval >= SIOMIX_HALF) ? 0 : SIOMIX_INTMAX;
|
|
||||||
}
|
|
||||||
switch (i->desc.type) {
|
|
||||||
case SIOMIX_NUM:
|
|
||||||
case SIOMIX_SW:
|
|
||||||
siomix_setctl(hdl, i->ctladdr, val);
|
|
||||||
break;
|
|
||||||
case SIOMIX_VEC:
|
|
||||||
case SIOMIX_LIST:
|
|
||||||
siomix_setctl(hdl, i->ctladdr, val);
|
|
||||||
}
|
|
||||||
i->curval = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* print all parameters
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
list(void)
|
|
||||||
{
|
|
||||||
struct info *p, *g;
|
|
||||||
|
|
||||||
for (g = infolist; g != NULL; g = nextgrp(g)) {
|
|
||||||
if (g->mode == MODE_IGNORE)
|
|
||||||
continue;
|
|
||||||
if (i_flag) {
|
|
||||||
if (v_flag) {
|
|
||||||
for (p = g; p != NULL; p = nextpar(p))
|
|
||||||
print_par(p, 0);
|
|
||||||
} else
|
|
||||||
print_par(g, 1);
|
|
||||||
} else {
|
|
||||||
if (v_flag || !ismono(g)) {
|
|
||||||
for (p = g; p != NULL; p = nextpar(p))
|
|
||||||
print_par(p, 0);
|
|
||||||
} else
|
|
||||||
print_par(g, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* register a new knob/button, called from the poll() loop. this may be
|
|
||||||
* called when label string changes, in which case we update the
|
|
||||||
* existing label widged rather than inserting a new one.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
ondesc(void *arg, struct siomix_desc *d, int curval)
|
|
||||||
{
|
|
||||||
struct info *i, **pi;
|
|
||||||
int cmp;
|
|
||||||
|
|
||||||
if (d == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* find the right position to insert the new widget
|
|
||||||
*/
|
|
||||||
for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
|
|
||||||
cmp = cmpdesc(d, &i->desc);
|
|
||||||
if (cmp == 0) {
|
|
||||||
/* label is updated */
|
|
||||||
if (i->desc.type == SIOMIX_LABEL) {
|
|
||||||
memcpy(i->desc.chan1.str, d->chan1.str,
|
|
||||||
SIOMIX_NAMEMAX);
|
|
||||||
print_par(i, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "fatal: duplicate mixer knob:\n");
|
|
||||||
print_par(i, 0);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if (cmp < 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i = malloc(sizeof(struct info));
|
|
||||||
if (i == NULL) {
|
|
||||||
perror("malloc");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
i->desc = *d;
|
|
||||||
i->ctladdr = d->addr;
|
|
||||||
i->curval = i->newval = curval;
|
|
||||||
i->mode = MODE_IGNORE;
|
|
||||||
i->next = *pi;
|
|
||||||
*pi = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* update a knob/button state, called from the poll() loop
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
onctl(void *arg, unsigned addr, unsigned val)
|
|
||||||
{
|
|
||||||
struct info *i;
|
|
||||||
|
|
||||||
if (v_flag >= 1)
|
|
||||||
fprintf(stderr, "onctl (%d, %d)\n", addr, val);
|
|
||||||
for (i = infolist; i != NULL; i = i->next) {
|
|
||||||
if (i->ctladdr != addr)
|
|
||||||
continue;
|
|
||||||
i->curval = val;
|
|
||||||
print_par(i, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
char *devname = SIOMIX_DEVANY;
|
|
||||||
int i, c, d_flag = 0;
|
|
||||||
struct info *g;
|
|
||||||
struct pollfd *pfds;
|
|
||||||
int nfds, revents;
|
|
||||||
|
|
||||||
while ((c = getopt(argc, argv, "df:imnv")) != -1) {
|
|
||||||
switch (c) {
|
|
||||||
case 'd':
|
|
||||||
d_flag = 1;
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
devname = optarg;
|
|
||||||
break;
|
|
||||||
case 'i':
|
|
||||||
i_flag = 1;
|
|
||||||
break;
|
|
||||||
case 'm':
|
|
||||||
m_flag = 1;
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
n_flag = 1;
|
|
||||||
break;
|
|
||||||
case 'v':
|
|
||||||
v_flag = 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fprintf(stderr, "usage: sndioctl "
|
|
||||||
"[-dimnv] [-f device] [command ...]\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
argc -= optind;
|
|
||||||
argv += optind;
|
|
||||||
|
|
||||||
hdl = siomix_open(devname, SIOMIX_READ | SIOMIX_WRITE, 0);
|
|
||||||
if (hdl == NULL) {
|
|
||||||
fprintf(stderr, "%s: can't open mixer device\n", devname);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if (!siomix_ondesc(hdl, ondesc, NULL)) {
|
|
||||||
fprintf(stderr, "%s: can't get mixer description\n", devname);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
siomix_onctl(hdl, onctl, NULL);
|
|
||||||
|
|
||||||
if (d_flag) {
|
|
||||||
if (argc > 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"commands are not allowed with -d option\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
dump();
|
|
||||||
} else {
|
|
||||||
if (argc == 0) {
|
|
||||||
for (g = infolist; g != NULL; g = nextgrp(g)) {
|
|
||||||
if (g->desc.type == SIOMIX_LABEL && !n_flag)
|
|
||||||
continue;
|
|
||||||
g->mode = MODE_PRINT;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i = 0; i < argc; i++) {
|
|
||||||
if (!cmd(argv[i]))
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
commit();
|
|
||||||
list();
|
|
||||||
}
|
|
||||||
if (m_flag) {
|
|
||||||
pfds = malloc(sizeof(struct pollfd) * siomix_nfds(hdl));
|
|
||||||
if (pfds == NULL) {
|
|
||||||
perror("malloc");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
for (;;) {
|
|
||||||
nfds = siomix_pollfd(hdl, pfds, POLLIN);
|
|
||||||
if (nfds == 0)
|
|
||||||
break;
|
|
||||||
while (poll(pfds, nfds, -1) < 0) {
|
|
||||||
if (errno != EINTR) {
|
|
||||||
perror("poll");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
revents = siomix_revents(hdl, pfds);
|
|
||||||
if (revents & POLLHUP) {
|
|
||||||
fprintf(stderr, "disconnected\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(pfds);
|
|
||||||
}
|
|
||||||
siomix_close(hdl);
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
# extra includes paths (-I options)
|
|
||||||
INCLUDE = -I/usr/X11R6/include -I../libsndio -I../bsd-compat
|
|
||||||
|
|
||||||
# extra libraries paths (-L options)
|
|
||||||
LIB = -L/usr/X11R6/lib -L../libsndio
|
|
||||||
|
|
||||||
# extra defines (-D options)
|
|
||||||
DEFS = -DDEBUG @defs@
|
|
||||||
|
|
||||||
# extra libraries (-l options)
|
|
||||||
LDADD = -lX11 -lsndio @ldadd@
|
|
||||||
|
|
||||||
# variables defined on configure script command line (if any)
|
|
||||||
@vars@
|
|
||||||
|
|
||||||
#
|
|
||||||
# binaries, documentation, man pages and examples will be installed in
|
|
||||||
# ${BIN_DIR}, ${MAN1_DIR}
|
|
||||||
#
|
|
||||||
BIN_DIR = @bindir@
|
|
||||||
MAN1_DIR = @mandir@/man1
|
|
||||||
|
|
||||||
#
|
|
||||||
# programs to build
|
|
||||||
#
|
|
||||||
PROG = xvolkeys
|
|
||||||
|
|
||||||
all: ${PROG}
|
|
||||||
|
|
||||||
install:
|
|
||||||
mkdir -p ${DESTDIR}${BIN_DIR} ${DESTDIR}${MAN1_DIR}
|
|
||||||
cp ${PROG} ${DESTDIR}${BIN_DIR}
|
|
||||||
cp ${PROG:=.1} ${DESTDIR}${MAN1_DIR}
|
|
||||||
|
|
||||||
uninstall:
|
|
||||||
cd ${DESTDIR}${BIN_DIR} && rm -f ${PROG}
|
|
||||||
cd ${DESTDIR}${MAN1_DIR} && rm -f ${PROG:=.1}
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f -- *.o xvolkeys
|
|
||||||
|
|
||||||
# ---------------------------------------------------------- dependencies ---
|
|
||||||
|
|
||||||
OBJS = xvolkeys.o
|
|
||||||
|
|
||||||
xvolkeys: ${OBJS}
|
|
||||||
${CC} ${LDFLAGS} ${LIB} -o xvolkeys ${OBJS} ${LDADD}
|
|
||||||
|
|
||||||
.c.o:
|
|
||||||
${CC} ${CFLAGS} ${INCLUDE} ${DEFS} -c $<
|
|
||||||
|
|
||||||
xvolkeys.o: xvolkeys.c
|
|
|
@ -1,54 +0,0 @@
|
||||||
.\" $OpenBSD$
|
|
||||||
.\"
|
|
||||||
.\" Copyright (c) 2014 Alexandre Ratchov <alex@caoua.org>
|
|
||||||
.\"
|
|
||||||
.\" Permission to use, copy, modify, and 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.
|
|
||||||
.\"
|
|
||||||
.Dd $Mdocdate$
|
|
||||||
.Dt XVOLKEYS 1
|
|
||||||
.Os
|
|
||||||
.Sh NAME
|
|
||||||
.Nm xvolkeys
|
|
||||||
.Nd hot-keys in X to control sndiod master volume
|
|
||||||
.Sh SYNOPSIS
|
|
||||||
.Nm xvolkeys
|
|
||||||
.Op Fl D
|
|
||||||
.Op Fl v
|
|
||||||
.Op Fl f Ar device
|
|
||||||
.Sh DESCRIPTION
|
|
||||||
The
|
|
||||||
.Nm
|
|
||||||
increments or
|
|
||||||
decrements the
|
|
||||||
.Xr sndiod 1
|
|
||||||
master volume whenever the "+" and "-" keys
|
|
||||||
are pressed while the Control and Alt keys are held down.
|
|
||||||
It is meant to be started as background process by
|
|
||||||
.Xr xinit 1
|
|
||||||
or
|
|
||||||
.Xr xdm 1
|
|
||||||
session startup scripts.
|
|
||||||
It exits when X session terminates.
|
|
||||||
.Pp
|
|
||||||
The options are as follows:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Fl D
|
|
||||||
Daemonize.
|
|
||||||
.It Fl f Ar device
|
|
||||||
Audio device to control volume of.
|
|
||||||
.It Fl v
|
|
||||||
Increase log verbosity.
|
|
||||||
.El
|
|
||||||
.Sh SEE ALSO
|
|
||||||
.Xr sndiod 1
|
|
||||||
.Xr sndio 7
|
|
|
@ -1,319 +0,0 @@
|
||||||
/* $OpenBSD$ */
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2014 Alexandre Ratchov <alex@caoua.org>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and 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.
|
|
||||||
*/
|
|
||||||
#include <poll.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sndio.h>
|
|
||||||
#include <X11/Xlib.h>
|
|
||||||
#include <X11/Xutil.h>
|
|
||||||
#include <X11/keysym.h>
|
|
||||||
|
|
||||||
/*
|
|
||||||
* X keysyms to increment / decrement volume: + and -
|
|
||||||
*/
|
|
||||||
#define KEY_INC XK_plus
|
|
||||||
#define KEY_DEC XK_minus
|
|
||||||
|
|
||||||
/*
|
|
||||||
* modifiers: Alt + Ctrl
|
|
||||||
*/
|
|
||||||
#define MODMASK (Mod1Mask | ControlMask)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* volume increment
|
|
||||||
*/
|
|
||||||
#define VOL_INC 9
|
|
||||||
|
|
||||||
char *dev_name;
|
|
||||||
struct pollfd pfds[16];
|
|
||||||
struct siomix_hdl *hdl;
|
|
||||||
unsigned int master_addr, master_val = SIOMIX_INTMAX;
|
|
||||||
int master_found = 0;
|
|
||||||
int verbose;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* X bits
|
|
||||||
*/
|
|
||||||
Display *dpy;
|
|
||||||
KeyCode inc_code, dec_code;
|
|
||||||
KeySym *inc_map, *dec_map;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* new control
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
mixer_ondesc(void *unused, struct siomix_desc *desc, int val)
|
|
||||||
{
|
|
||||||
if (desc == NULL)
|
|
||||||
return;
|
|
||||||
if (master_found)
|
|
||||||
return;
|
|
||||||
if (strcmp(desc->chan0.str, "sndiod0") == 0 &&
|
|
||||||
strcmp(desc->func, "level") == 0) {
|
|
||||||
master_found = 1;
|
|
||||||
master_addr = desc->addr;
|
|
||||||
master_val = val;
|
|
||||||
if (verbose)
|
|
||||||
fprintf(stderr, "%s: master at addr %u, value = %u\n",
|
|
||||||
dev_name, master_addr, master_val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* control value changed
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
mixer_onctl(void *unused, unsigned int addr, unsigned int val)
|
|
||||||
{
|
|
||||||
if (addr == master_addr) {
|
|
||||||
if (verbose)
|
|
||||||
fprintf(stderr, "master changed %u -> %u\n", master_val, val);
|
|
||||||
master_val = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* if there's an error, close connection to sndiod
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
mixer_disconnect(void)
|
|
||||||
{
|
|
||||||
if (!siomix_eof(hdl))
|
|
||||||
return;
|
|
||||||
if (verbose)
|
|
||||||
fprintf(stderr, "%s: mixer device disconnected\n", dev_name);
|
|
||||||
siomix_close(hdl);
|
|
||||||
hdl = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* connect to sndiod
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
mixer_connect(void)
|
|
||||||
{
|
|
||||||
if (hdl != NULL)
|
|
||||||
return 1;
|
|
||||||
hdl = siomix_open(dev_name, SIOMIX_READ | SIOMIX_WRITE, 0);
|
|
||||||
if (hdl == NULL) {
|
|
||||||
if (verbose)
|
|
||||||
fprintf(stderr, "%s: couldn't open mixer device\n",
|
|
||||||
dev_name);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
master_found = 0;
|
|
||||||
siomix_ondesc(hdl, mixer_ondesc, NULL);
|
|
||||||
siomix_onctl(hdl, mixer_onctl, NULL);
|
|
||||||
if (!master_found)
|
|
||||||
fprintf(stderr, "%s: warning, couldn't find master control\n",
|
|
||||||
dev_name);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* send master volume message and to the server
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
mixer_incrvol(int incr)
|
|
||||||
{
|
|
||||||
int vol;
|
|
||||||
|
|
||||||
if (!mixer_connect())
|
|
||||||
return;
|
|
||||||
vol = master_val + incr;
|
|
||||||
if (vol > SIOMIX_INTMAX)
|
|
||||||
vol = SIOMIX_INTMAX;
|
|
||||||
if (vol < 0)
|
|
||||||
vol = 0;
|
|
||||||
if (master_val != (unsigned int)vol) {
|
|
||||||
master_val = vol;
|
|
||||||
if (hdl && master_found) {
|
|
||||||
if (verbose) {
|
|
||||||
fprintf(stderr, "%s: setting volume to %d\n",
|
|
||||||
dev_name, vol);
|
|
||||||
}
|
|
||||||
siomix_setctl(hdl, master_addr, master_val);
|
|
||||||
mixer_disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* register hot-keys in X
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
grab_keys(void)
|
|
||||||
{
|
|
||||||
unsigned int i, scr, nscr;
|
|
||||||
int nret;
|
|
||||||
|
|
||||||
if (verbose)
|
|
||||||
fprintf(stderr, "grabbing keys\n");
|
|
||||||
inc_code = XKeysymToKeycode(dpy, KEY_INC);
|
|
||||||
inc_map = XGetKeyboardMapping(dpy, inc_code, 1, &nret);
|
|
||||||
if (nret <= ShiftMask) {
|
|
||||||
fprintf(stderr, "couldn't get keymap for '+' key\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
dec_code = XKeysymToKeycode(dpy, KEY_DEC);
|
|
||||||
dec_map = XGetKeyboardMapping(dpy, dec_code, 1, &nret);
|
|
||||||
if (nret <= ShiftMask) {
|
|
||||||
fprintf(stderr, "couldn't get keymap for '-' key\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
nscr = ScreenCount(dpy);
|
|
||||||
for (i = 0; i <= 0xff; i++) {
|
|
||||||
if ((i & MODMASK) != 0)
|
|
||||||
continue;
|
|
||||||
for (scr = 0; scr != nscr; scr++) {
|
|
||||||
|
|
||||||
XGrabKey(dpy, inc_code, i | MODMASK,
|
|
||||||
RootWindow(dpy, scr), 1,
|
|
||||||
GrabModeAsync, GrabModeAsync);
|
|
||||||
XGrabKey(dpy, dec_code, i | MODMASK,
|
|
||||||
RootWindow(dpy, scr), 1,
|
|
||||||
GrabModeAsync, GrabModeAsync);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* unregister hot-keys
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
ungrab_keys(void)
|
|
||||||
{
|
|
||||||
unsigned int scr, nscr;
|
|
||||||
|
|
||||||
if (verbose)
|
|
||||||
fprintf(stderr, "ungrabbing keys\n");
|
|
||||||
|
|
||||||
XFree(inc_map);
|
|
||||||
XFree(dec_map);
|
|
||||||
|
|
||||||
nscr = ScreenCount(dpy);
|
|
||||||
for (scr = 0; scr != nscr; scr++)
|
|
||||||
XUngrabKey(dpy, AnyKey, AnyModifier, RootWindow(dpy, scr));
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
int scr;
|
|
||||||
XEvent xev;
|
|
||||||
int c, nfds;
|
|
||||||
int background, revents;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* parse command line options
|
|
||||||
*/
|
|
||||||
dev_name = SIOMIX_DEVANY;
|
|
||||||
verbose = 0;
|
|
||||||
background = 0;
|
|
||||||
while ((c = getopt(argc, argv, "Df:q:v")) != -1) {
|
|
||||||
switch (c) {
|
|
||||||
case 'D':
|
|
||||||
background = 1;
|
|
||||||
break;
|
|
||||||
case 'v':
|
|
||||||
verbose++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'q': /* compat */
|
|
||||||
case 'f':
|
|
||||||
dev_name = optarg;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
goto bad_usage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
argc -= optind;
|
|
||||||
argv += optind;
|
|
||||||
if (argc > 0) {
|
|
||||||
bad_usage:
|
|
||||||
fprintf(stderr, "usage: xvolkeys [-Dv] [-f device]\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
dpy = XOpenDisplay(NULL);
|
|
||||||
if (dpy == 0) {
|
|
||||||
fprintf(stderr, "Couldn't open display\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* mask non-key events for each screan */
|
|
||||||
for (scr = 0; scr != ScreenCount(dpy); scr++)
|
|
||||||
XSelectInput(dpy, RootWindow(dpy, scr), KeyPress);
|
|
||||||
|
|
||||||
(void)mixer_connect();
|
|
||||||
|
|
||||||
grab_keys();
|
|
||||||
|
|
||||||
if (background) {
|
|
||||||
verbose = 0;
|
|
||||||
if (daemon(0, 0) < 0) {
|
|
||||||
perror("daemon");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
while (XPending(dpy)) {
|
|
||||||
XNextEvent(dpy, &xev);
|
|
||||||
if (xev.type == MappingNotify) {
|
|
||||||
if (xev.xmapping.request != MappingKeyboard)
|
|
||||||
continue;
|
|
||||||
if (verbose)
|
|
||||||
fprintf(stderr, "keyboard remapped\n");
|
|
||||||
ungrab_keys();
|
|
||||||
grab_keys();
|
|
||||||
}
|
|
||||||
if (xev.type != KeyPress)
|
|
||||||
continue;
|
|
||||||
if (xev.xkey.keycode == inc_code &&
|
|
||||||
inc_map[xev.xkey.state & ShiftMask] == KEY_INC) {
|
|
||||||
mixer_incrvol(VOL_INC);
|
|
||||||
} else if (xev.xkey.keycode == dec_code &&
|
|
||||||
dec_map[xev.xkey.state & ShiftMask] == KEY_DEC) {
|
|
||||||
mixer_incrvol(-VOL_INC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nfds = (hdl != NULL) ? siomix_pollfd(hdl, pfds, 0) : 0;
|
|
||||||
pfds[nfds].fd = ConnectionNumber(dpy);
|
|
||||||
pfds[nfds].events = POLLIN;
|
|
||||||
while (poll(pfds, nfds + 1, -1) < 0 && errno == EINTR)
|
|
||||||
; /* nothing */
|
|
||||||
if (hdl) {
|
|
||||||
revents = siomix_revents(hdl, pfds);
|
|
||||||
if (revents & POLLHUP)
|
|
||||||
mixer_disconnect();
|
|
||||||
else if (revents & POLLIN) {
|
|
||||||
/* what */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
XFree(inc_map);
|
|
||||||
XFree(dec_map);
|
|
||||||
XCloseDisplay(dpy);
|
|
||||||
if (hdl)
|
|
||||||
siomix_close(hdl);
|
|
||||||
return 0;
|
|
||||||
}
|
|
Loading…
Reference in New Issue