/*compile-command: set -x cc -g -O2 -Wall "$0" -lasound -lX11 -o "${0/.c}" exit $? */ #include #include #include #include #include #include #include #include #include #include #include #define lenof(a) (sizeof((a)) / sizeof(*(a))) static Display *disp; static Window root; static snd_seq_t *seq; static snd_seq_addr_t port; static int stop; static void sighandler(int sig_unused) { stop = 1; } static int open_display(void) { const char *dispname = getenv("DISPLAY"); if (!dispname || !*dispname) { dispname = ":0.0"; } disp = XOpenDisplay(dispname); if (!disp) { fprintf(stderr, "Failed to open X11 display %s\n", dispname); return 0; } root = XDefaultRootWindow(disp); return 1; } static void send_key(int keysym, int down) { Window focus; int revert; XGetInputFocus(disp, &focus, &revert); XKeyEvent event = { .type = down ? KeyPress : KeyRelease, .display = disp, .window = focus, .root = root, .subwindow = None, .time = CurrentTime, .x = 0, .y = 0, .x_root = 0, .y_root = 0, .same_screen = True, .keycode = XKeysymToKeycode(disp, keysym), .state = 0, }; XSendEvent(disp, focus, True, KeyPressMask, (XEvent *)&event); XFlush(disp); } static int open_seq(const char *port_name) { int err; if ((err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0)) < 0) { fprintf(stderr, "snd_seq_open(default): %s\n", snd_strerror(err)); return 0; } if ((err = snd_seq_parse_address(seq, &port, port_name)) < 0) { fprintf(stderr, "snd_seq_parse_address(%s): %s\n", port_name, snd_strerror(err)); return 0; } if ((err = snd_seq_create_simple_port( seq, "seqtokbd", SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION)) < 0) { fprintf(stderr, "snd_seq_create_simple_port(): %s\n", snd_strerror(err)); return 0; } if ((err = snd_seq_connect_from(seq, 0, port.client, port.port)) < 0) { fprintf(stderr, "snd_seq_connect_from(%s): %s\n", port_name, snd_strerror(err)); return 0; } return 1; } static snd_seq_event_t *get_event(void) { int npfds = snd_seq_poll_descriptors_count(seq, POLLIN); struct pollfd *pfds = alloca(sizeof(*pfds) * npfds); snd_seq_event_t *event; do { snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN); if (poll(pfds, npfds, -1) < 0) { if (errno != EINTR) { perror("poll"); } return NULL; } int err; if ((err = snd_seq_event_input(seq, &event)) < 0) { fprintf(stderr, "snd_seq_event_input(): %s", snd_strerror(err)); return NULL; } } while (!event); return event; } static void handle_event(const snd_seq_event_t *event) { static int keymap[] = { [48] = XK_A, [49] = XK_S, [50] = XK_D, [51] = XK_F, [52] = XK_G, [53] = XK_H, [54] = XK_J, [55] = XK_K, [56] = XK_L, [57] = XK_semicolon, [58] = XK_colon, [59] = XK_bracketright, [60] = XK_Q, [61] = XK_W, [62] = XK_E, [63] = XK_R, [64] = XK_T, [65] = XK_Y, [66] = XK_U, [67] = XK_I, [68] = XK_O, [69] = XK_P, [70] = XK_at, [71] = XK_bracketleft, [72] = XK_1, [73] = XK_2, [74] = XK_3, [75] = XK_4, [76] = XK_5, [77] = XK_6, [78] = XK_7, [79] = XK_8, [80] = XK_9, [81] = XK_0, [82] = XK_minus, [83] = XK_asciicircum, }; if (event->type == SND_SEQ_EVENT_NOTEON || event->type == SND_SEQ_EVENT_NOTEOFF) { int down; if (event->type == SND_SEQ_EVENT_NOTEON && event->data.note.velocity > 0) { printf("ON %3d %3d\n", event->data.note.note, event->data.note.velocity); down = 1; } else { printf("off %3d\n", event->data.note.note); down = 0; } if (event->data.note.note < lenof(keymap) && keymap[event->data.note.note] != 0) { send_key(keymap[event->data.note.note], down); /* Space out keypresses for programs that can't handle * multiple keys at once. */ if (down) { usleep(20000); } } } } int main(int argc, char **argv) { if (argc != 2 || argv[1][0] == '-') { fprintf(stderr, "Usage: %s PORT-NUM\n", argv[0]); return 2; } if (!open_display()) { return 1; } if (!open_seq(argv[1])) { return 1; } stop = 0; signal(SIGHUP, sighandler); signal(SIGINT, sighandler); signal(SIGTERM, sighandler); while (!stop) { snd_seq_event_t *event; if (!(event = get_event())) { break; } handle_event(event); } snd_seq_close(seq); return 0; }