add xvolkeys utility

This commit is contained in:
Alexandre Ratchov 2014-02-04 11:53:39 +01:00
parent f7d028191a
commit 28654d372c
5 changed files with 521 additions and 1 deletions

3
configure vendored
View File

@ -160,7 +160,8 @@ if [ $rmidi = yes ]; then
fi
for f in Makefile aucat/Makefile sndiod/Makefile \
libsndio/Makefile sndioctl/Makefile examples/Makefile \
libsndio/Makefile sndioctl/Makefile xvolkeys/Makefile \
examples/Makefile \
contrib/init.d.sndiod
do
sed \

53
xvolkeys/Makefile.in Normal file
View File

@ -0,0 +1,53 @@
# 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
MAN1 = xvolkeys.1
all: ${PROG} ${MAN1}
install:
mkdir -p ${DESTDIR}${BIN_DIR} ${DESTDIR}${MAN1_DIR}
cp xvolkeys ${DESTDIR}${BIN_DIR}
cp xvolkeys.1 ${DESTDIR}${MAN1_DIR}
uninstall:
cd ${DESTDIR}${BIN_DIR} && rm -f ${PROG}
cd ${DESTDIR}${MAN1_DIR} && rm -f ${MAN1}
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 sysex.h

125
xvolkeys/sysex.h Normal file
View File

@ -0,0 +1,125 @@
/* $OpenBSD$ */
/*
* Copyright (c) 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.
*/
#ifndef AUCAT_SYSEX_H
#define AUCAT_SYSEX_H
#include <stdint.h>
/*
* start and end markers
*/
#define SYSEX_START 0xf0
#define SYSEX_END 0xf7
/*
* type/vendor namespace IDs we use
*/
#define SYSEX_TYPE_RT 0x7f /* real-time universal */
#define SYSEX_TYPE_EDU 0x7d /* non-comercial */
/*
* realtime messages in the "universal real-time" namespace
*/
#define SYSEX_MTC 0x01 /* mtc messages */
#define SYSEX_MTC_FULL 0x01 /* mtc full frame message */
#define SYSEX_CONTROL 0x04
#define SYSEX_MASTER 0x01
#define SYSEX_MMC 0x06
#define SYSEX_MMC_STOP 0x01
#define SYSEX_MMC_START 0x02
#define SYSEX_MMC_LOC 0x44
#define SYSEX_MMC_LOC_LEN 0x06
#define SYSEX_MMC_LOC_CMD 0x01
/*
* sepcial "any" midi device number
*/
#define SYSEX_DEV_ANY 0x7f
/*
* aucat-specific messages, in the "edu" namespace
*/
#define SYSEX_AUCAT 0x23 /* aucat-specific */
#define SYSEX_AUCAT_SLOTDESC 0x01 /* mixer info */
#define SYSEX_AUCAT_DUMPREQ 0x02 /* dump request */
#define SYSEX_AUCAT_DUMPEND 0x03 /* end of dump */
/*
* minimum size of sysex message we accept
*/
#define SYSEX_SIZE(m) (5 + sizeof(struct sysex_ ## m))
/*
* all possible system exclusive messages we support. For aucat-specific
* messages we use the same header as real-time messages to simplify the
* message parser
*/
struct sysex {
uint8_t start;
uint8_t type; /* type or vendor id */
uint8_t dev; /* device or product id */
uint8_t id0; /* message id */
uint8_t id1; /* sub-id */
union sysex_all {
struct sysex_empty {
uint8_t end;
} empty;
struct sysex_master {
uint8_t fine;
uint8_t coarse;
uint8_t end;
} master;
struct sysex_start {
uint8_t end;
} start;
struct sysex_stop {
uint8_t end;
} stop;
struct sysex_loc {
uint8_t len;
uint8_t cmd;
uint8_t hr;
uint8_t min;
uint8_t sec;
uint8_t fr;
uint8_t cent;
uint8_t end;
} loc;
struct sysex_full {
uint8_t hr;
uint8_t min;
uint8_t sec;
uint8_t fr;
uint8_t end;
} full;
struct sysex_slotdesc {
uint8_t chan; /* channel */
uint8_t vol; /* current volume */
#define SYSEX_NAMELEN 10 /* \0 included */
uint8_t name[SYSEX_NAMELEN]; /* stream name */
uint8_t end;
} slotdesc;
struct sysex_dumpreq {
uint8_t end;
} dumpreq;
struct sysex_dumpend {
uint8_t end;
} dumpend;
} u;
};
#endif /* !defined(AUCAT_SYSEX_H) */

63
xvolkeys/xvolkeys.1 Normal file
View File

@ -0,0 +1,63 @@
.\" $OpenBSD$
.\"
.\" Copyright (c) 2010-2012 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 November 29, 2010
.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 port
.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.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl D
Daemonize.
.It Fl f Ar dev
MIDI port to use, default is
.Pa snd/0
.It Fl v
Increase log verbosity.
.El
.Sh SEE ALSO
.Xr sndiod 1
.Xr sndio 7
.Sh BUGS
The
.Nm
utility keeps the audio device open.
If the device is disconnected or
.Xr sndiod 1
is restarted, the
.Nm
process terminates.

278
xvolkeys/xvolkeys.c Normal file
View File

@ -0,0 +1,278 @@
#include <poll.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sndio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include "sysex.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)
/*
* max MIDI message length we accept
*/
#define MIDI_MSGMAX (sizeof(struct sysex))
/*
* max MIDI volume
*/
#define MIDI_MAXVOL 127
/*
* midi parser state
*/
struct mio_hdl *hdl;
unsigned int midx;
unsigned char mev[MIDI_MSGMAX];
int master = MIDI_MAXVOL;
int verbose;
unsigned char dumpreq[] = {
SYSEX_START,
SYSEX_TYPE_EDU,
0,
SYSEX_AUCAT,
SYSEX_AUCAT_DUMPREQ,
SYSEX_END
};
void
midi_setvol(int vol)
{
struct sysex msg;
if (vol > MIDI_MAXVOL)
vol = MIDI_MAXVOL;
if (vol < 0)
vol = 0;
if (verbose)
fprintf(stderr, "setvol(%d)\n", vol);
if (master != vol) {
msg.start = SYSEX_START;
msg.type = SYSEX_TYPE_RT;
msg.dev = SYSEX_DEV_ANY;
msg.id0 = SYSEX_CONTROL;
msg.id1 = SYSEX_MASTER;
msg.u.master.fine = 0;
msg.u.master.coarse = vol;
msg.u.master.end = SYSEX_END;
if (mio_write(hdl, &msg, SYSEX_SIZE(master)) == 0) {
fprintf(stderr, "Couldn't write volume message\n");
exit(1);
}
master = vol;
}
}
/*
* decode midi sysex message
*/
void
midi_sysex(unsigned char *msg, unsigned len)
{
int i;
if (verbose >= 2) {
fprintf(stderr, "sysex: ");
for (i = 0; i < len; i++)
fprintf(stderr, " %02x", msg[i]);
fprintf(stderr, "\n");
}
if (len == 8 &&
msg[1] == SYSEX_TYPE_RT &&
msg[3] == SYSEX_CONTROL &&
msg[4] == SYSEX_MASTER) {
if (master != msg[6]) {
master = msg[6];
fprintf(stderr, "master -> %d\n", master);
}
}
}
void
midi_parse(unsigned char *mbuf, unsigned len)
{
unsigned c;
for (; len > 0; len--) {
c = *mbuf++;
if (c >= 0xf8) {
/* ignore */
} else if (c == SYSEX_END) {
if (mev[0] == SYSEX_START) {
mev[midx++] = c;
midi_sysex(mev, midx);
}
mev[0] = 0;
} else if (c == SYSEX_START) {
mev[0] = c;
midx = 1;
} else if (c >= 0x80) {
mev[0] = 0;
} else if (mev[0]) {
if (midx < MIDI_MSGMAX - 1) {
mev[midx++] = c;
} else
mev[0] = 0;
}
}
}
void
usage(void)
{
fprintf(stderr, "usage: xvolkeys [-Dv] [-f device]\n");
exit(1);
}
int
main(int argc, char **argv)
{
#define MIDIBUFSZ 0x100
unsigned char msg[MIDIBUFSZ];
char *devname;
struct pollfd *pfds;
unsigned int i, scr, nscr;
Display *dpy;
KeyCode inc_code, dec_code;
KeySym *inc_map, *dec_map;
XEvent xev;
int c, nfds, n, nret;
int background;
/*
* parse command line options
*/
devname = "snd/0";
verbose = 0;
background = 0;
while ((c = getopt(argc, argv, "Df:v")) != -1) {
switch (c) {
case 'D':
background = 1;
break;
case 'v':
verbose++;
break;
case 'f':
devname = optarg;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc > 0)
usage();
hdl = mio_open("snd/0", MIO_IN | MIO_OUT, 0);
if (hdl == NULL) {
fprintf(stderr, "Couldn't open MIDI control device\n");
exit(1);
}
dpy = XOpenDisplay(NULL);
if (dpy == 0) {
fprintf(stderr, "Couldn't open display\n");
exit(1);
}
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);
}
}
/* mask non-key events for each screan */
for (scr = 0; scr != nscr; scr++)
XSelectInput(dpy, RootWindow(dpy, scr), KeyPress);
/*
* request initial volume
*/
if (mio_write(hdl, dumpreq, sizeof(dumpreq)) != sizeof(dumpreq)) {
fprintf(stderr, "Failed to request master volume\n");
exit(1);
}
pfds = malloc(sizeof(struct pollfd) * (mio_nfds(hdl) + 1));
if (pfds == NULL) {
fprintf(stderr, "Failed to allocate pollfd structures\n");
exit(1);
}
if (background) {
verbose = 0;
if (daemon(0, 0) < 0) {
perror("daemon");
exit(1);
}
}
while (1) {
while (XPending(dpy)) {
XNextEvent(dpy, &xev);
if (xev.type != KeyPress)
continue;
if (xev.xkey.keycode == inc_code &&
inc_map[xev.xkey.state & ShiftMask] == KEY_INC) {
midi_setvol(master + 5);
} else if (xev.xkey.keycode == dec_code &&
dec_map[xev.xkey.state & ShiftMask] == KEY_DEC) {
midi_setvol(master - 5);
}
}
nfds = mio_pollfd(hdl, pfds, POLLIN);
pfds[nfds].fd = ConnectionNumber(dpy);
pfds[nfds].events = POLLIN;
while (poll(pfds, nfds + 1, -1) < 0 && errno == EINTR)
; /* nothing */
if (mio_revents(hdl, pfds) & POLLIN) {
n = mio_read(hdl, msg, MIDIBUFSZ);
if (n == 0)
break;
midi_parse(msg, n);
}
}
XFree(inc_map);
XFree(dec_map);
XCloseDisplay(dpy);
mio_close(hdl);
return 0;
}