move the mixer api to a dedicated "mixer" branch

This commit is contained in:
Alexandre Ratchov 2015-08-28 08:59:59 +02:00
parent e8007feb00
commit 1e958d6853
6 changed files with 0 additions and 1560 deletions

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;
}