2014-02-06 03:16:33 -06:00
|
|
|
/* $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.
|
|
|
|
*/
|
2014-02-04 04:53:39 -06:00
|
|
|
#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
|
|
|
|
*/
|
2014-02-06 03:16:33 -06:00
|
|
|
char *port;
|
|
|
|
struct pollfd pfds[16];
|
2014-02-04 04:53:39 -06:00
|
|
|
struct mio_hdl *hdl;
|
|
|
|
unsigned int midx;
|
|
|
|
unsigned char mev[MIDI_MSGMAX];
|
|
|
|
|
|
|
|
int master = MIDI_MAXVOL;
|
|
|
|
int verbose;
|
|
|
|
|
|
|
|
unsigned char dumpreq[] = {
|
2014-02-06 03:16:33 -06:00
|
|
|
SYSEX_START,
|
2014-02-04 04:53:39 -06:00
|
|
|
SYSEX_TYPE_EDU,
|
|
|
|
0,
|
|
|
|
SYSEX_AUCAT,
|
|
|
|
SYSEX_AUCAT_DUMPREQ,
|
|
|
|
SYSEX_END
|
|
|
|
};
|
|
|
|
|
2014-02-04 06:03:59 -06:00
|
|
|
/*
|
|
|
|
* X bits
|
|
|
|
*/
|
|
|
|
Display *dpy;
|
|
|
|
KeyCode inc_code, dec_code;
|
|
|
|
KeySym *inc_map, *dec_map;
|
|
|
|
|
2014-02-06 03:22:47 -06:00
|
|
|
/*
|
|
|
|
* connect to sndiod
|
|
|
|
*/
|
2014-02-06 03:16:33 -06:00
|
|
|
int
|
|
|
|
midi_connect(void)
|
|
|
|
{
|
2014-02-06 05:25:32 -06:00
|
|
|
if (hdl != NULL)
|
2014-02-14 08:19:44 -06:00
|
|
|
return 1;
|
2014-02-06 03:16:33 -06:00
|
|
|
hdl = mio_open(port, MIO_IN | MIO_OUT, 0);
|
|
|
|
if (hdl == NULL) {
|
|
|
|
if (verbose)
|
|
|
|
fprintf(stderr, "%s: couldn't open MIDI port\n", port);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-02-06 03:22:47 -06:00
|
|
|
/*
|
|
|
|
* if there's an error, close connection to sndiod
|
|
|
|
*/
|
2014-02-06 03:16:33 -06:00
|
|
|
void
|
|
|
|
midi_disconnect(void)
|
|
|
|
{
|
|
|
|
if (!mio_eof(hdl))
|
|
|
|
return;
|
|
|
|
if (verbose)
|
2014-02-06 03:27:52 -06:00
|
|
|
fprintf(stderr, "%s: MIDI port disconnected\n", port);
|
2014-02-06 03:16:33 -06:00
|
|
|
mio_close(hdl);
|
|
|
|
hdl = NULL;
|
|
|
|
}
|
|
|
|
|
2014-02-06 03:22:47 -06:00
|
|
|
/*
|
|
|
|
* send master volume message and to the server
|
|
|
|
*/
|
2014-02-04 04:53:39 -06:00
|
|
|
void
|
|
|
|
midi_setvol(int vol)
|
|
|
|
{
|
|
|
|
struct sysex msg;
|
|
|
|
|
|
|
|
if (vol > MIDI_MAXVOL)
|
2014-02-06 03:16:33 -06:00
|
|
|
vol = MIDI_MAXVOL;
|
2014-02-04 04:53:39 -06:00
|
|
|
if (vol < 0)
|
|
|
|
vol = 0;
|
|
|
|
if (verbose)
|
2014-02-06 03:16:33 -06:00
|
|
|
fprintf(stderr, "%s: setting volume to %d\n", port, vol);
|
|
|
|
if (!midi_connect())
|
|
|
|
return;
|
2014-02-04 04:53:39 -06:00
|
|
|
if (master != vol) {
|
2014-02-06 03:16:33 -06:00
|
|
|
master = vol;
|
2014-02-04 04:53:39 -06:00
|
|
|
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;
|
2014-02-06 03:16:33 -06:00
|
|
|
if (hdl) {
|
|
|
|
mio_write(hdl, &msg, SYSEX_SIZE(master));
|
|
|
|
midi_disconnect();
|
2014-02-04 04:53:39 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* decode midi sysex message
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
midi_sysex(unsigned char *msg, unsigned len)
|
|
|
|
{
|
|
|
|
if (len == 8 &&
|
|
|
|
msg[1] == SYSEX_TYPE_RT &&
|
|
|
|
msg[3] == SYSEX_CONTROL &&
|
|
|
|
msg[4] == SYSEX_MASTER) {
|
|
|
|
if (master != msg[6]) {
|
|
|
|
master = msg[6];
|
2014-02-06 03:16:33 -06:00
|
|
|
if (verbose) {
|
|
|
|
fprintf(stderr, "%s: volume is %d\n",
|
|
|
|
port, master);
|
|
|
|
}
|
2014-02-04 04:53:39 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-06 03:22:47 -06:00
|
|
|
/*
|
|
|
|
* decode midi bytes
|
|
|
|
*/
|
2014-02-04 04:53:39 -06:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-06 03:22:47 -06:00
|
|
|
/*
|
|
|
|
* register hot-keys in X
|
|
|
|
*/
|
2014-02-04 06:03:59 -06:00
|
|
|
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) {
|
2014-02-06 03:27:52 -06:00
|
|
|
fprintf(stderr, "couldn't get keymap for '+' key\n");
|
2014-02-04 06:03:59 -06:00
|
|
|
exit(1);
|
|
|
|
}
|
2014-02-06 03:16:33 -06:00
|
|
|
|
2014-02-04 06:03:59 -06:00
|
|
|
dec_code = XKeysymToKeycode(dpy, KEY_DEC);
|
|
|
|
dec_map = XGetKeyboardMapping(dpy, dec_code, 1, &nret);
|
|
|
|
if (nret <= ShiftMask) {
|
2014-02-06 03:27:52 -06:00
|
|
|
fprintf(stderr, "couldn't get keymap for '-' key\n");
|
2014-02-04 06:03:59 -06:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
nscr = ScreenCount(dpy);
|
|
|
|
for (i = 0; i <= 0xff; i++) {
|
|
|
|
if ((i & MODMASK) != 0)
|
|
|
|
continue;
|
|
|
|
for (scr = 0; scr != nscr; scr++) {
|
2014-02-06 03:16:33 -06:00
|
|
|
|
2014-02-04 06:03:59 -06:00
|
|
|
XGrabKey(dpy, inc_code, i | MODMASK,
|
|
|
|
RootWindow(dpy, scr), 1,
|
|
|
|
GrabModeAsync, GrabModeAsync);
|
|
|
|
XGrabKey(dpy, dec_code, i | MODMASK,
|
|
|
|
RootWindow(dpy, scr), 1,
|
|
|
|
GrabModeAsync, GrabModeAsync);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-06 03:22:47 -06:00
|
|
|
/*
|
|
|
|
* unregister hot-keys
|
|
|
|
*/
|
2014-02-04 06:03:59 -06:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2014-02-04 04:53:39 -06:00
|
|
|
void
|
|
|
|
usage(void)
|
|
|
|
{
|
2014-02-05 11:07:49 -06:00
|
|
|
fprintf(stderr, "usage: xvolkeys [-Dv] [-q port]\n");
|
2014-02-04 04:53:39 -06:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
#define MIDIBUFSZ 0x100
|
|
|
|
unsigned char msg[MIDIBUFSZ];
|
2014-02-06 03:22:47 -06:00
|
|
|
unsigned int scr;
|
2014-02-04 04:53:39 -06:00
|
|
|
XEvent xev;
|
2014-02-04 06:03:59 -06:00
|
|
|
int c, nfds, n;
|
2014-02-06 03:27:52 -06:00
|
|
|
int background, revents;
|
2014-02-04 04:53:39 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* parse command line options
|
|
|
|
*/
|
2014-02-06 03:16:33 -06:00
|
|
|
port = "snd/0";
|
2014-02-04 04:53:39 -06:00
|
|
|
verbose = 0;
|
|
|
|
background = 0;
|
2014-02-05 11:07:49 -06:00
|
|
|
while ((c = getopt(argc, argv, "Dq:v")) != -1) {
|
2014-02-04 04:53:39 -06:00
|
|
|
switch (c) {
|
|
|
|
case 'D':
|
|
|
|
background = 1;
|
|
|
|
break;
|
|
|
|
case 'v':
|
|
|
|
verbose++;
|
|
|
|
break;
|
2014-02-05 11:07:49 -06:00
|
|
|
case 'q':
|
2014-02-06 03:16:33 -06:00
|
|
|
port = optarg;
|
2014-02-04 04:53:39 -06:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
usage();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
argc -= optind;
|
|
|
|
argv += optind;
|
|
|
|
if (argc > 0)
|
|
|
|
usage();
|
|
|
|
|
|
|
|
dpy = XOpenDisplay(NULL);
|
|
|
|
if (dpy == 0) {
|
|
|
|
fprintf(stderr, "Couldn't open display\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mask non-key events for each screan */
|
2014-02-04 06:03:59 -06:00
|
|
|
for (scr = 0; scr != ScreenCount(dpy); scr++)
|
2014-02-04 04:53:39 -06:00
|
|
|
XSelectInput(dpy, RootWindow(dpy, scr), KeyPress);
|
|
|
|
|
2014-02-06 03:16:33 -06:00
|
|
|
(void)midi_connect();
|
|
|
|
|
2014-02-04 04:53:39 -06:00
|
|
|
/*
|
|
|
|
* request initial volume
|
|
|
|
*/
|
2014-02-06 03:16:33 -06:00
|
|
|
if (hdl) {
|
|
|
|
mio_write(hdl, dumpreq, sizeof(dumpreq));
|
|
|
|
midi_disconnect();
|
2014-02-04 04:53:39 -06:00
|
|
|
}
|
|
|
|
|
2014-02-04 06:03:59 -06:00
|
|
|
grab_keys();
|
|
|
|
|
2014-02-04 04:53:39 -06:00
|
|
|
if (background) {
|
|
|
|
verbose = 0;
|
|
|
|
if (daemon(0, 0) < 0) {
|
|
|
|
perror("daemon");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
while (XPending(dpy)) {
|
|
|
|
XNextEvent(dpy, &xev);
|
2014-02-04 06:03:59 -06:00
|
|
|
if (xev.type == MappingNotify) {
|
|
|
|
if (xev.xmapping.request != MappingKeyboard)
|
|
|
|
continue;
|
|
|
|
if (verbose)
|
|
|
|
fprintf(stderr, "keyboard remapped\n");
|
|
|
|
ungrab_keys();
|
|
|
|
grab_keys();
|
|
|
|
}
|
2014-02-04 04:53:39 -06:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2014-02-06 03:16:33 -06:00
|
|
|
nfds = (hdl != NULL) ? mio_pollfd(hdl, pfds, POLLIN) : 0;
|
2014-02-04 04:53:39 -06:00
|
|
|
pfds[nfds].fd = ConnectionNumber(dpy);
|
|
|
|
pfds[nfds].events = POLLIN;
|
|
|
|
while (poll(pfds, nfds + 1, -1) < 0 && errno == EINTR)
|
|
|
|
; /* nothing */
|
2014-02-06 03:16:33 -06:00
|
|
|
if (hdl) {
|
2014-02-06 03:27:52 -06:00
|
|
|
revents = mio_revents(hdl, pfds);
|
|
|
|
if (revents & POLLHUP)
|
|
|
|
midi_disconnect();
|
|
|
|
else if (revents & POLLIN) {
|
2014-02-06 03:16:33 -06:00
|
|
|
n = mio_read(hdl, msg, MIDIBUFSZ);
|
|
|
|
midi_parse(msg, n);
|
|
|
|
}
|
2014-02-04 04:53:39 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
XFree(inc_map);
|
|
|
|
XFree(dec_map);
|
|
|
|
XCloseDisplay(dpy);
|
2014-02-06 03:16:33 -06:00
|
|
|
if (hdl)
|
|
|
|
mio_close(hdl);
|
2014-02-04 04:53:39 -06:00
|
|
|
return 0;
|
|
|
|
}
|