From 28654d372c3539bfccaf261bcaf6b106989460f1 Mon Sep 17 00:00:00 2001 From: Alexandre Ratchov Date: Tue, 4 Feb 2014 11:53:39 +0100 Subject: [PATCH] add xvolkeys utility --- configure | 3 +- xvolkeys/Makefile.in | 53 +++++++++ xvolkeys/sysex.h | 125 +++++++++++++++++++ xvolkeys/xvolkeys.1 | 63 ++++++++++ xvolkeys/xvolkeys.c | 278 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 xvolkeys/Makefile.in create mode 100644 xvolkeys/sysex.h create mode 100644 xvolkeys/xvolkeys.1 create mode 100644 xvolkeys/xvolkeys.c diff --git a/configure b/configure index b46737f..3375294 100755 --- a/configure +++ b/configure @@ -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 \ diff --git a/xvolkeys/Makefile.in b/xvolkeys/Makefile.in new file mode 100644 index 0000000..1ced968 --- /dev/null +++ b/xvolkeys/Makefile.in @@ -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 diff --git a/xvolkeys/sysex.h b/xvolkeys/sysex.h new file mode 100644 index 0000000..4366ee5 --- /dev/null +++ b/xvolkeys/sysex.h @@ -0,0 +1,125 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2011 Alexandre Ratchov + * + * 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 + +/* + * 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) */ diff --git a/xvolkeys/xvolkeys.1 b/xvolkeys/xvolkeys.1 new file mode 100644 index 0000000..3708f70 --- /dev/null +++ b/xvolkeys/xvolkeys.1 @@ -0,0 +1,63 @@ +.\" $OpenBSD$ +.\" +.\" Copyright (c) 2010-2012 Alexandre Ratchov +.\" +.\" 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. diff --git a/xvolkeys/xvolkeys.c b/xvolkeys/xvolkeys.c new file mode 100644 index 0000000..5f8e0d3 --- /dev/null +++ b/xvolkeys/xvolkeys.c @@ -0,0 +1,278 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +}