mirror of https://github.com/ericonr/sndio.git
add xvolkeys utility
This commit is contained in:
parent
f7d028191a
commit
28654d372c
|
@ -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 \
|
||||
|
|
|
@ -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
|
|
@ -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) */
|
|
@ -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.
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue