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