/*-
 * Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * jack-keyboard is a virtual keyboard for JACK MIDI.
 *
 * For questions and comments, you can contact:
 * - Edward Tomasz Napierala <trasz@FreeBSD.org>
 * - Hans Petter Selasky <hselasky@FreeBSD.org>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <sysexits.h>
#include <errno.h>
#include <jack/jack.h>
#include <jack/midiport.h>
#include <jack/ringbuffer.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "pianokeyboard.h"

#ifdef HAVE_LASH
#include <lash/lash.h>
#endif

#ifdef HAVE_X11
#include <gdk/gdkx.h>
#endif

#define VELOCITY_MAX		127
#define VELOCITY_HIGH		100
#define VELOCITY_NORMAL		64
#define VELOCITY_LOW		32
#define VELOCITY_MIN		1

#define MOD_MIN				0
#define MOD_MAX				127
#define MOD_INIT			0

#define PITCH_MIN			-100
#define PITCH_MAX			100
#define PITCH_INIT			0
#define PITCH_RANGE			8192

#define OUTPUT_PORT_NAME	"midi_out"
#define INPUT_PORT_NAME		"midi_in"
#define PACKAGE_NAME		"jack-keyboard"
#define PACKAGE_VERSION		"2.7.2"

jack_port_t	*output_port;
jack_port_t	*input_port;
int		entered_number = -1;
int		allow_connecting_to_own_kind = 0;
int		enable_gui = 1;
int		grab_keyboard_at_startup = 0;
volatile int	keyboard_grabbed = 0;
int		enable_window_title = 0;
int		time_offsets_are_zero = 0;
int		send_program_change_at_reconnect = 0;
int		send_program_change_once = 0;
int		program_change_was_sent = 0;
int		velocity_high = VELOCITY_HIGH;
int		velocity_normal = VELOCITY_NORMAL;
int		velocity_low = VELOCITY_LOW;
int		*current_velocity = &velocity_normal;
int		octave = 4;
double		rate_limit = 0.0;
jack_client_t	*jack_client = NULL;

#ifdef HAVE_LASH
lash_client_t	*lash_client;
#endif

static void panic(void);

#define MIDI_NOTE_ON		0x90
#define MIDI_NOTE_OFF		0x80
#define MIDI_PROGRAM_CHANGE	0xC0
#define MIDI_CONTROLLER		0xB0
#define MIDI_PITCH		0xE0
#define MIDI_RESET		0xFF
#define MIDI_HOLD_PEDAL		64
#define MIDI_ALL_SOUND_OFF	120
#define MIDI_ALL_MIDI_CONTROLLERS_OFF	121
#define MIDI_ALL_NOTES_OFF	123
#define MIDI_BANK_SELECT_MSB	0
#define MIDI_BANK_SELECT_LSB	32
#define MIDI_MOD_CC		1

#define BANK_MIN		0
#define BANK_MAX		127
#define PROGRAM_MIN		0
#define PROGRAM_MAX		127
#define CHANNEL_MIN		1
#define CHANNEL_MAX		16

const char *rc_style = "style \"default\"\n"
"{\n"
"	xthickness		 = 3\n"
"	ythickness		 = 2\n"
"	fg[NORMAL]		= \"#fff\"\n"
"	fg[PRELIGHT]	  = \"#fff\"\n"
"	fg[ACTIVE]		= \"#fff\"\n"
"	fg[SELECTED]	  = \"#fff\"\n"
"	fg[INSENSITIVE]   = \"#aaa\"\n"
"	bg[NORMAL]		= \"#222\"\n"
"	bg[PRELIGHT]	  = \"#444\"\n"
"	bg[ACTIVE]		= \"#222\"\n"
"	bg[SELECTED]	  = \"#0af\"\n"
"	bg[INSENSITIVE]   = \"#666\"\n"
"	base[NORMAL]	  = \"#000\"\n"
"	base[PRELIGHT]	= \"#444\"\n"
"	base[ACTIVE]	  = \"#444\"\n"
"	base[SELECTED]	= \"#444\"\n"
"	base[INSENSITIVE] = \"#333\"\n"
"	text[NORMAL]	  = \"#fff\"\n"
"	text[PRELIGHT]	= \"#fff\"\n"
"	text[ACTIVE]	  = \"#f80\"\n"
"	text[SELECTED]	= \"#f80\"\n"
"	text[INSENSITIVE] = \"#666\"\n"
"}\n"
"style \"button\"\n"
"{\n"
"	fg[NORMAL]		= \"#fff\"\n"
"	bg[NORMAL]		= \"#000\"\n"
"	bg[PRELIGHT]	  = \"#333\"\n"
"	bg[ACTIVE]		= \"#444\"\n"
"	bg[INSENSITIVE]   = \"#222\"\n"
"}\n"
"style \"scale\"\n"
"{\n"
"	bg[NORMAL]		= \"#555\"\n"
"	bg[PRELIGHT]	  = \"#000\"\n"
"	bg[ACTIVE]		= \"#000\"\n"
"	bg[INSENSITIVE]   = \"#333\"\n"
"}\n"
"style \"label\"\n"
"{\n"
"	fg[NORMAL]		= \"#aaa\"\n"
"}\n"
"widget \"*\" style : rc \"default\"\n"
"widget_class\"*Gtk*Scale*\" style : highest \"scale\"\n"
"widget_class\"*GtkLabel*\" style : highest \"label\"\n"
"widget_class\"*GtkButton*\" style : highest \"button\"\n"
"widget_class\"*GtkToggle*\" style : highest \"button\"\n";

GtkWidget	*window, *sustain_button, *channel_spin, *bank_spin, *program_spin, *connected_to_combo,
		*velocity_scale, *grab_keyboard_checkbutton, *octave_spin, *mod_scale, *pitch_scale, *panic_button;
PianoKeyboard	*keyboard;
GtkListStore	*connected_to_store;

#ifdef HAVE_X11
Display		*dpy;
#endif

struct MidiMessage {
	jack_nframes_t	time;
	int		len;	/* Length of MIDI message, in bytes. */
	unsigned char	data[3];
};

#define RINGBUFFER_SIZE		1024*sizeof(struct MidiMessage)

/* Will emit a warning if time between jack callbacks is longer than this. */
#define MAX_TIME_BETWEEN_CALLBACKS	0.1

/* Will emit a warning if execution of jack callback takes longer than this. */
#define MAX_PROCESSING_TIME	0.01

jack_ringbuffer_t *ringbuffer;

/* Number of currently used program. */
int		program = 0;

/* Number of currently selected bank. */
int		bank = 0;

/* Number of currently selected channel (0..15). */
int		channel = 0;

void draw_note(int key);
void queue_message(struct MidiMessage *ev);

double
get_time(void)
{
	double seconds;
	int ret;
	struct timeval tv;

	ret = gettimeofday(&tv, NULL);

	if (ret) {
		perror("gettimeofday");
		exit(EX_OSERR);
	}

	seconds = tv.tv_sec + tv.tv_usec / 1000000.0;

	return (seconds);
}

double
get_delta_time(void)
{
	static double previously = -1.0;
	double now;
	double delta;

	now = get_time();

	if (previously == -1.0) {
		previously = now;

		return (0);
	}

	delta = now - previously;
	previously = now;

	assert(delta >= 0.0);

	return (delta);
}

gboolean
process_received_message_async(gpointer evp)
{
	int i;
	struct MidiMessage *ev = (struct MidiMessage *)evp;
	int b0 = ev->data[0];
	int b1 = ev->data[1];

	/* Strip channel from channel messages */
	if (b0 >= 0x80 && b0 <= 0xEF)
		b0 = b0 & 0xF0;

	if (b0 == MIDI_RESET || (b0 == MIDI_CONTROLLER &&
		(b1 == MIDI_ALL_NOTES_OFF || b1 == MIDI_ALL_SOUND_OFF))) {

		for (i = 0; i < NNOTES; i++) {
			piano_keyboard_set_note_off(keyboard, i);
		}
	}

	if (b0 == MIDI_NOTE_ON) {
		if (ev->data[2] == 0)
			piano_keyboard_set_note_off(keyboard, ev->data[1]);
		else
			piano_keyboard_set_note_on(keyboard, ev->data[1], ev->data[2]);
	}

	if (b0 == MIDI_NOTE_OFF) {
		piano_keyboard_set_note_off(keyboard, ev->data[1]);
	}

	ev->data [0] = b0 | channel;
	queue_message(ev);

	return (FALSE);
}

struct MidiMessage *
midi_message_from_midi_event(jack_midi_event_t event)
{
	struct MidiMessage *ev = malloc(sizeof(*ev));

	if (ev == NULL) {
		perror("malloc");
		return (NULL);
	}

	assert(event.size >= 1 && event.size <= 3);

	ev->len = event.size;
	ev->time = event.time;

	memcpy(ev->data, event.buffer, ev->len);

	return (ev);
}

gboolean
warning_async(gpointer s)
{
	const char *str = (const char *)s;

	g_warning("%s", str);

	return (FALSE);
}

void
warn_from_jack_thread_context(const char *str)
{
	g_idle_add(warning_async, (gpointer)str);
}

void
process_midi_input(jack_nframes_t nframes)
{
	int read, events, i;
	void *port_buffer;
	jack_midi_event_t event;

	port_buffer = jack_port_get_buffer(input_port, nframes);
	if (port_buffer == NULL) {
		warn_from_jack_thread_context("jack_port_get_buffer failed, cannot receive anything.");
		return;
	}

#ifdef JACK_MIDI_NEEDS_NFRAMES
	events = jack_midi_get_event_count(port_buffer, nframes);
#else
	events = jack_midi_get_event_count(port_buffer);
#endif

	for (i = 0; i < events; i++) {
		struct MidiMessage *rev;

#ifdef JACK_MIDI_NEEDS_NFRAMES
		read = jack_midi_event_get(&event, port_buffer, i, nframes);
#else
		read = jack_midi_event_get(&event, port_buffer, i);
#endif
		if (read) {
			warn_from_jack_thread_context("jack_midi_event_get failed, RECEIVED NOTE LOST.");
			continue;
		}

		if (event.size > 3) {
			warn_from_jack_thread_context("Ignoring MIDI message longer than three bytes, probably a SysEx.");
			continue;
		}

		rev = midi_message_from_midi_event(event);
		if (rev == NULL) {
			warn_from_jack_thread_context("midi_message_from_midi_event failed, RECEIVED NOTE LOST.");
			continue;
		}

		g_idle_add(process_received_message_async, rev);
	}
}

double
nframes_to_ms(jack_nframes_t nframes)
{
	jack_nframes_t sr;

	sr = jack_get_sample_rate(jack_client);

	assert(sr > 0);

	return ((nframes * 1000.0) / (double)sr);
}

void
process_midi_output(jack_nframes_t nframes)
{
	int read, t, bytes_remaining;
	unsigned char *buffer;
	void *port_buffer;
	jack_nframes_t last_frame_time;
	struct MidiMessage ev;

	last_frame_time = jack_last_frame_time(jack_client);

	port_buffer = jack_port_get_buffer(output_port, nframes);
	if (port_buffer == NULL) {
		warn_from_jack_thread_context("jack_port_get_buffer failed, cannot send anything.");
		return;
	}

#ifdef JACK_MIDI_NEEDS_NFRAMES
	jack_midi_clear_buffer(port_buffer, nframes);
#else
	jack_midi_clear_buffer(port_buffer);
#endif

	/* We may push at most one byte per 0.32ms to stay below 31.25 Kbaud limit. */
	bytes_remaining = nframes_to_ms(nframes) * rate_limit;

	while (jack_ringbuffer_read_space(ringbuffer)) {
		read = jack_ringbuffer_peek(ringbuffer, (char *)&ev, sizeof(ev));

		if (read != sizeof(ev)) {
			warn_from_jack_thread_context("Short read from the ringbuffer, possible note loss.");
			jack_ringbuffer_read_advance(ringbuffer, read);
			continue;
		}

		bytes_remaining -= ev.len;

		if (rate_limit > 0.0 && bytes_remaining <= 0) {
			warn_from_jack_thread_context("Rate limiting in effect.");
			break;
		}

		t = ev.time + nframes - last_frame_time;

		/* If computed time is too much into the future, we'll need
		   to send it later. */
		if (t >= (int)nframes)
			break;

		/* If computed time is < 0, we missed a cycle because of xrun. */
		if (t < 0)
			t = 0;

		if (time_offsets_are_zero)
			t = 0;

		jack_ringbuffer_read_advance(ringbuffer, sizeof(ev));

#ifdef JACK_MIDI_NEEDS_NFRAMES
		buffer = jack_midi_event_reserve(port_buffer, t, ev.len, nframes);
#else
		buffer = jack_midi_event_reserve(port_buffer, t, ev.len);
#endif

		if (buffer == NULL) {
			warn_from_jack_thread_context("jack_midi_event_reserve failed, NOTE LOST.");
			break;
		}

		memcpy(buffer, ev.data, ev.len);
	}
}

int
process_callback(jack_nframes_t nframes, void *notused)
{
#ifdef MEASURE_TIME
	if (get_delta_time() > MAX_TIME_BETWEEN_CALLBACKS)
		warn_from_jack_thread_context("Had to wait too long for JACK callback; scheduling problem?");
#endif

	/* Check for impossible condition that actually happened to me, caused by some problem between jackd and OSS4. */
	if (nframes <= 0) {
		warn_from_jack_thread_context("Process callback called with nframes = 0; bug in JACK?");
		return 0;
	}

	process_midi_input(nframes);
	process_midi_output(nframes);

#ifdef MEASURE_TIME
	if (get_delta_time() > MAX_PROCESSING_TIME)
		warn_from_jack_thread_context("Processing took too long; scheduling problem?");
#endif

	return (0);
}

void
queue_message(struct MidiMessage *ev)
{
	int written;

	if (jack_ringbuffer_write_space(ringbuffer) < sizeof(*ev)) {
		g_critical("Not enough space in the ringbuffer, NOTE LOST.");
		return;
	}

	written = jack_ringbuffer_write(ringbuffer, (char *)ev, sizeof(*ev));

	if (written != sizeof(*ev))
		g_warning("jack_ringbuffer_write failed, NOTE LOST.");
}

void
queue_new_message(int b0, int b1, int b2)
{
	struct MidiMessage ev;

	/* For MIDI messages that specify a channel number, filter the original
	   channel number out and add our own. */
	if (b0 >= 0x80 && b0 <= 0xEF) {
		b0 &= 0xF0;
		b0 += channel;
	}

	if (b1 == -1) {
		ev.len = 1;
		ev.data[0] = b0;

	} else if (b2 == -1) {
		ev.len = 2;
		ev.data[0] = b0;
		ev.data[1] = b1;

	} else {
		ev.len = 3;
		ev.data[0] = b0;
		ev.data[1] = b1;
		ev.data[2] = b2;
	}

	ev.time = jack_frame_time(jack_client);

	queue_message(&ev);
}

gboolean
update_connected_to_combo_async(gpointer notused)
{
	int i, count = 0;
	const char **connected, **available, *my_name;
	GtkTreeIter iter;

	if (jack_client == NULL || output_port == NULL)
		return (FALSE);

	connected = jack_port_get_connections(output_port);
	available = jack_get_ports(jack_client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput);
	my_name = jack_port_name(input_port);

	assert(my_name);

	/* There will be at least one listening MIDI port - the one we create. */
	assert(available);

	if (available != NULL) {
		gtk_list_store_clear(connected_to_store);

		for (i = 0; available[i] != NULL; i++) {
			if (!strcmp(available[i], my_name) || (!allow_connecting_to_own_kind &&
			    !strncmp(available[i], my_name, strlen(PACKAGE_NAME))))

				continue;

			count++;

			gtk_list_store_append(connected_to_store, &iter);
			gtk_list_store_set(connected_to_store, &iter, 0, available[i], -1);

			if (connected != NULL && connected[0] != NULL && !strcmp(available[i], connected[0]))
				gtk_combo_box_set_active_iter(GTK_COMBO_BOX(connected_to_combo), &iter);
		}
	}

	if (count > 0)
		gtk_widget_set_sensitive(connected_to_combo, TRUE);
	else
		gtk_widget_set_sensitive(connected_to_combo, FALSE);

	if (connected != NULL)
		free(connected);

	free(available);

	return (FALSE);
}

void
draw_window_title(void)
{
	int i, off = 0;
	char title[256];
	const char **connected_ports;

	if (window == NULL)
		return;

	if (enable_window_title && jack_client != NULL) {
		connected_ports = jack_port_get_connections(output_port);

		off += snprintf(title, sizeof(title) - off, "%s: channel %d, bank %d, program %d",
		    jack_get_client_name(jack_client), channel + 1, bank, program);

		if (!program_change_was_sent)
			off += snprintf(title + off, sizeof(title) - off, " (bank/program change not sent)");

		if (connected_ports == NULL || connected_ports[0] == NULL) {
			off += snprintf(title + off, sizeof(title) - off, ", NOT CONNECTED");

		} else {
			off += snprintf(title + off, sizeof(title) - off, ", connected to ");

			for (i = 0; connected_ports[i] != NULL; i++) {
				off += snprintf(title + off, sizeof(title) - off, "%s%s",
				    i == 0 ? "" : ", ", connected_ports[i]);
			}
		}

		if (connected_ports != NULL)
			free(connected_ports);

		gtk_window_set_title(GTK_WINDOW(window), title);
	} else {
		/* May be null if JACK is not initialized yet. */
		if (jack_client != NULL)
			gtk_window_set_title(GTK_WINDOW(window), jack_get_client_name(jack_client));
		else
			gtk_window_set_title(GTK_WINDOW(window), PACKAGE_NAME);
	}
}

gboolean
update_window_title_async(gpointer notused)
{
	if (window != NULL)
		draw_window_title();

	return (FALSE);
}

int
graph_order_callback(void *notused)
{
	g_idle_add(update_window_title_async, NULL);
	g_idle_add(update_connected_to_combo_async, NULL);

	return (0);
}

void
send_program_change(void)
{
	if (jack_port_connected(output_port) == 0)
		return;

	queue_new_message(MIDI_CONTROLLER, MIDI_BANK_SELECT_LSB, bank % 128);
	queue_new_message(MIDI_CONTROLLER, MIDI_BANK_SELECT_MSB, bank / 128);
	queue_new_message(MIDI_PROGRAM_CHANGE, program, -1);

	program_change_was_sent = 1;
}

/* Connects to the specified input port, disconnecting already connected ports. */
int
connect_to_input_port(const char *port)
{
        int ret;

	if (!strcmp(port, jack_port_name(input_port)))
		return (-1);

	if (!allow_connecting_to_own_kind) {
		if (!strncmp(port, jack_port_name(input_port), strlen(PACKAGE_NAME)))
			return (-2);
	}

        ret = jack_port_disconnect(jack_client, output_port);
        if (ret) {
                g_warning("Cannot disconnect MIDI port.");

                return (-3);
        }

        ret = jack_connect(jack_client, jack_port_name(output_port), port);
        if (ret) {
                g_warning("Cannot connect to %s.", port);

                return (-4);
        }

	g_warning("Connected to %s.", port);

	program_change_was_sent = 0;

	if (send_program_change_at_reconnect || send_program_change_once) {
		send_program_change();
		draw_window_title();
	}

	send_program_change_once = 0;

	return (0);
}

void
connect_to_another_input_port(int up_not_down)
{
	const char **available_midi_ports;
	const char **connected_ports;
	const char *current = NULL;
	int i, max, current_index;

	available_midi_ports = jack_get_ports(jack_client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput);

	/* There will be at least one listening MIDI port - the one we create. */
	assert(available_midi_ports);

	/*
	 * max is the highest possible index into available_midi_ports[],
	 * i.e. number of elements - 1.
	 */
	for (max = 0; available_midi_ports[max + 1] != NULL; max++);

	/* Only one input port - our own. */
	if (max == 0) {
		g_warning("No listening JACK MIDI input ports found.  "
		    "Run some softsynth and press Insert or Delete key.");

		return;
	}

	connected_ports = jack_port_get_connections(output_port);

	if (connected_ports != NULL && connected_ports[0] != NULL)
		current = connected_ports[0];
	else
		current = available_midi_ports[0];

	/*
	 * current is the index of currently connected port into available_midi_ports[].
	 */
	for (i = 0; i <= max && strcmp(available_midi_ports[i], current); i++);

	current_index = i;

	assert(!strcmp(available_midi_ports[current_index], current));

	/* XXX: rewrite. */
	if (up_not_down) {
		for (i = current_index + 1; i <= max; i++) {
			assert(available_midi_ports[i] != NULL);

			if (connect_to_input_port(available_midi_ports[i]) == 0)
				goto connected;
		}

		for (i = 0; i <= current_index; i++) {
			assert(available_midi_ports[i] != NULL);

			if (connect_to_input_port(available_midi_ports[i]) == 0)
				goto connected;
		}

	} else {
		for (i = current_index - 1; i >= 0; i--) {
			assert(available_midi_ports[i] != NULL);

			if (connect_to_input_port(available_midi_ports[i]) == 0)
				goto connected;
		}

		for (i = max; i >= current_index; i--) {
			assert(available_midi_ports[i] != NULL);

			if (connect_to_input_port(available_midi_ports[i]) == 0)
				goto connected;
		}
	}

	g_warning("Cannot connect to any of the input ports.");

connected:
	free(available_midi_ports);

	if (connected_ports != NULL)
		free(connected_ports);
}

void
connect_to_next_input_port(void)
{
	connect_to_another_input_port(1);
}

void
connect_to_prev_input_port(void)
{
	connect_to_another_input_port(0);
}

void
init_jack(void)
{
	int err;

#ifdef HAVE_LASH
	lash_event_t *event;
#endif

	jack_client = jack_client_open(PACKAGE_NAME, JackNoStartServer, NULL);

	if (jack_client == NULL) {
		g_critical("Could not connect to the JACK server; run jackd first?");
		exit(EX_UNAVAILABLE);
	}

	ringbuffer = jack_ringbuffer_create(RINGBUFFER_SIZE);

	if (ringbuffer == NULL) {
		g_critical("Cannot create JACK ringbuffer.");
		exit(EX_SOFTWARE);
	}

	jack_ringbuffer_mlock(ringbuffer);

#ifdef HAVE_LASH
	event = lash_event_new_with_type(LASH_Client_Name);
	assert (event); /* Documentation does not say anything about return value. */
	lash_event_set_string(event, jack_get_client_name(jack_client));
	lash_send_event(lash_client, event);

	lash_jack_client_name(lash_client, jack_get_client_name(jack_client));
#endif

	err = jack_set_process_callback(jack_client, process_callback, 0);
	if (err) {
		g_critical("Could not register JACK process callback.");
		exit(EX_UNAVAILABLE);
	}

	err = jack_set_graph_order_callback(jack_client, graph_order_callback, 0);
	if (err) {
		g_critical("Could not register JACK graph order callback.");
		exit(EX_UNAVAILABLE);
	}

	output_port = jack_port_register(jack_client, OUTPUT_PORT_NAME, JACK_DEFAULT_MIDI_TYPE,
		JackPortIsOutput, 0);

	if (output_port == NULL) {
		g_critical("Could not register JACK output port.");
		exit(EX_UNAVAILABLE);
	}

	input_port = jack_port_register(jack_client, INPUT_PORT_NAME, JACK_DEFAULT_MIDI_TYPE,
		JackPortIsInput, 0);

	if (input_port == NULL) {
		g_critical("Could not register JACK input port.");
		exit(EX_UNAVAILABLE);
	}

	if (jack_activate(jack_client)) {
		g_critical("Cannot activate JACK client.");
		exit(EX_UNAVAILABLE);
	}
}

#ifdef HAVE_LASH

void
load_config_from_lash(void)
{
	lash_config_t *config;
	const char *key;
	int value;

	while ((config = lash_get_config(lash_client))) {

		key = lash_config_get_key(config);
		value = lash_config_get_value_int(config);

		if (!strcmp(key, "channel")) {
			if (value < CHANNEL_MIN || value > CHANNEL_MAX) {
				g_warning("Bad value '%d' for 'channel' property received from LASH.", value);
			} else {
				gtk_spin_button_set_value(GTK_SPIN_BUTTON(channel_spin), value);
			}

		} else if (!strcmp(key, "bank")) {
			if (value < BANK_MIN || value > BANK_MAX) {
				g_warning("Bad value '%d' for 'bank' property received from LASH.", value);
			} else {
				gtk_spin_button_set_value(GTK_SPIN_BUTTON(bank_spin), value);
			}

		} else if (!strcmp(key, "program")) {
			if (value < PROGRAM_MIN || value > PROGRAM_MAX) {
				g_warning("Bad value '%d' for 'program' property received from LASH.", value);
			} else {
				gtk_spin_button_set_value(GTK_SPIN_BUTTON(program_spin), value);
			}

		} else if (!strcmp(key, "keyboard_grabbed")) {
			if (value < 0 || value > 1) {
				g_warning("Bad value '%d' for 'keyboard_grabbed' property received from LASH.", value);
			} else {
				gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grab_keyboard_checkbutton), value);
			}

		} else if (!strcmp(key, "octave")) {
			if (value < OCTAVE_MIN || value > OCTAVE_MAX) {
				g_warning("Bad value '%d' for 'octave' property received from LASH.", value);
			} else {
				gtk_spin_button_set_value(GTK_SPIN_BUTTON(octave_spin), value);
			}

		} else if (!strcmp(key, "velocity_normal")) {
			if (value < VELOCITY_MIN || value > VELOCITY_MAX) {
				g_warning("Bad value '%d' for 'velocity_normal' property received from LASH.", value);
			} else {
		                velocity_normal = value;
				gtk_range_set_value(GTK_RANGE(velocity_scale), *current_velocity);
			}

		} else if (!strcmp(key, "velocity_high")) {
			if (value < VELOCITY_MIN || value > VELOCITY_MAX) {
				g_warning("Bad value '%d' for 'velocity_high' property received from LASH.", value);
			} else {
		                velocity_high = value;
				gtk_range_set_value(GTK_RANGE(velocity_scale), *current_velocity);
			}

		} else if (!strcmp(key, "velocity_low")) {
			if (value < VELOCITY_MIN || value > VELOCITY_MAX) {
				g_warning("Bad value '%d' for 'velocity_low' property received from LASH.", value);
			} else {
		                velocity_low = value;
				gtk_range_set_value(GTK_RANGE(velocity_scale), *current_velocity);
			}

		} else {
			g_warning("Received unknown config key '%s' (value '%d') from LASH.", key, value);
		}

		lash_config_destroy(config);
	}
}

void
save_config_int(const char *name, int value)
{
	lash_config_t *config;

	config = lash_config_new_with_key(name);
	lash_config_set_value_int(config, value);
	lash_send_config(lash_client, config);
}

void
save_config_into_lash(void)
{
	save_config_int("channel", channel + 1);
	save_config_int("bank", bank);
	save_config_int("program", program);
	save_config_int("keyboard_grabbed", keyboard_grabbed);
	save_config_int("octave", octave);
	save_config_int("velocity_normal", velocity_normal);
	save_config_int("velocity_high", velocity_high);
	save_config_int("velocity_low", velocity_low);
}

gboolean
lash_callback(gpointer notused)
{
	lash_event_t *event;

	while ((event = lash_get_event(lash_client))) {
		switch (lash_event_get_type(event)) {
		case LASH_Restore_Data_Set:
			load_config_from_lash();
			lash_send_event(lash_client, event);
			break;

		case LASH_Save_Data_Set:
			save_config_into_lash();
			lash_send_event(lash_client, event);
			break;

		case LASH_Quit:
			g_warning("Exiting due to LASH request.");
			exit(EX_OK);
			break;

		default:
			g_warning("Receieved unknown LASH event of type %d.", lash_event_get_type(event));
			lash_event_destroy(event);
		}
	}

	return (TRUE);
}

void
init_lash(lash_args_t *args)
{
	/* XXX: Am I doing the right thing wrt protocol version? */
	lash_client = lash_init(args, PACKAGE_NAME, LASH_Config_Data_Set, LASH_PROTOCOL(2, 0));

	if (!lash_server_connected(lash_client)) {
		g_critical("Cannot initialize LASH.  Continuing anyway.");
		/* exit(EX_UNAVAILABLE); */

		return;
	}

	/* Schedule a function to process LASH events, ten times per second. */
	g_timeout_add(100, lash_callback, NULL);
}

#endif /* HAVE_LASH */

gboolean
sustain_event_handler(GtkToggleButton *widget, gpointer pressed)
{
	if (pressed) {
		gtk_toggle_button_set_active(widget, TRUE);
		piano_keyboard_sustain_press(keyboard);
	} else {
		gtk_toggle_button_set_active(widget, FALSE);
		piano_keyboard_sustain_release(keyboard);
	}

	return (FALSE);
}

void
channel_event_handler(GtkSpinButton *spinbutton, gpointer notused)
{
	channel = gtk_spin_button_get_value(spinbutton) - 1;

	draw_window_title();
}

void
bank_event_handler(GtkSpinButton *spinbutton, gpointer notused)
{
	bank = gtk_spin_button_get_value(spinbutton);

	send_program_change();
	draw_window_title();
}

void
program_event_handler(GtkSpinButton *spinbutton, gpointer notused)
{
	program = gtk_spin_button_get_value(spinbutton);

	send_program_change();
	draw_window_title();
}

void
connected_to_event_handler(GtkComboBox *widget, gpointer notused)
{
	GtkTreeIter iter;
	gchar *connect_to;
	const char **connected_ports;

	if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(connected_to_combo), &iter) == FALSE)
		return;

	gtk_tree_model_get(GTK_TREE_MODEL(connected_to_store), &iter, 0, &connect_to, -1);

	connected_ports = jack_port_get_connections(output_port);
	if (connected_ports != NULL && connected_ports[0] != NULL && !strcmp(connect_to, connected_ports[0])) {
		free(connected_ports);
		return;
	}

	connect_to_input_port(connect_to);

	free(connected_ports);
}

void
velocity_event_handler(GtkRange *range, gpointer notused)
{
	assert(current_velocity);

	*current_velocity = gtk_range_get_value(range);
	keyboard->current_velocity = gtk_range_get_value(range);
}

void
mod_event_handler(GtkRange *range, gpointer notused)
{
	int val = (int) gtk_range_get_value(range);
	queue_new_message(MIDI_CONTROLLER, MIDI_MOD_CC, val);
}

void
pitch_event_handler(GtkRange *range, gpointer notused)
{
	uint16_t val = (uint16_t) (gtk_range_get_value(range) * ((float)PITCH_RANGE / (float)PITCH_MAX) + (float)PITCH_RANGE);
	queue_new_message(MIDI_PITCH, val & 127, (val >> 7) & 127);
}

void
panic_event_handler(GtkWidget *widget)
{
	panic();
}

#ifdef HAVE_X11

int grab_x_error_handler(Display *dpy, XErrorEvent *notused)
{
	keyboard_grabbed = 0;

	return (42);  /* Returned value is ignored. */
}

GdkFilterReturn
keyboard_grab_filter(GdkXEvent *xevent, GdkEvent *event, gpointer notused)
{
	XEvent *xe;
	XKeyEvent *xke;

	xe = (XEvent *)xevent;

	if (xe->type != KeyPress && xe->type != KeyRelease)
		return (GDK_FILTER_CONTINUE);

	xke = (XKeyEvent *)xevent;

	/* Lie to GDK, pretending we are the proper recipient of this XEvent.  Without it,
	   GDK would discard it. */
	xke->window = GDK_WINDOW_XWINDOW(window->window);

	return (GDK_FILTER_CONTINUE);
}

void
ungrab_keyboard(void)
{
	static int (*standard_x_error_handler)(Display *dpy, XErrorEvent *notused);

	standard_x_error_handler = XSetErrorHandler(grab_x_error_handler);

	XUngrabKey(dpy, AnyKey, AnyModifier, GDK_ROOT_WINDOW());
	XSync(dpy, FALSE);

	XSetErrorHandler(standard_x_error_handler);

	keyboard_grabbed = 0;
}

void
grab_keyboard(void)
{
	int i;
	static int (*standard_x_error_handler)(Display *dpy, XErrorEvent *notused);

	KeySym keys_to_grab[] = {
		XK_1, XK_2, XK_3, XK_4, XK_5, XK_6, XK_7, XK_8, XK_9, XK_0, XK_minus, XK_equal,
		XK_q, XK_w, XK_e, XK_r, XK_t, XK_y, XK_u, XK_i, XK_o, XK_p,
		XK_a, XK_s, XK_d, XK_f, XK_g, XK_h, XK_j, XK_k, XK_l,
		XK_z, XK_x, XK_c, XK_v, XK_b, XK_n, XK_m,
		XK_Shift_L, XK_Shift_R, XK_Control_L, XK_Control_R, XK_space,
		XK_Insert, XK_Delete, XK_Home, XK_End, XK_Page_Up, XK_Page_Down,
		XK_KP_1, XK_KP_2, XK_KP_3, XK_KP_4, XK_KP_5, XK_KP_6, XK_KP_7, XK_KP_8, XK_KP_9, XK_KP_0,
		XK_Num_Lock, XK_KP_Multiply, XK_KP_Divide, XK_KP_Subtract, XK_KP_Add, XK_KP_Enter,
		XK_Escape,

		/* End of list. */
		XK_VoidSymbol};

	if (keyboard_grabbed)
		return;

	dpy = GDK_WINDOW_XDISPLAY(window->window);

	keyboard_grabbed = 1;

	standard_x_error_handler = XSetErrorHandler(grab_x_error_handler);

	for (i = 0; keys_to_grab[i] != XK_VoidSymbol; i++) {
		KeyCode kc = XKeysymToKeycode(dpy, keys_to_grab[i]);

		if (kc == 0) {
			g_critical("XKeysymToKeycode failed.  Please report this to the author.");
			continue;
		}

		XGrabKey(dpy, kc, 0, GDK_ROOT_WINDOW(), True, GrabModeAsync, GrabModeAsync);
		XGrabKey(dpy, kc, ShiftMask, GDK_ROOT_WINDOW(), True, GrabModeAsync, GrabModeAsync);
		XGrabKey(dpy, kc, ControlMask, GDK_ROOT_WINDOW(), True, GrabModeAsync, GrabModeAsync);
		XGrabKey(dpy, kc, ShiftMask | ControlMask, GDK_ROOT_WINDOW(), True, GrabModeAsync, GrabModeAsync);
	}

	XSync(dpy, FALSE);

	XSetErrorHandler(standard_x_error_handler);

	if (keyboard_grabbed == 0) {
                g_critical("XGrabKey() failed; keyboard grabbing not possible.  "
				"Maybe some other application grabbed the keyboard?");
		/* Make sure we don't keep any keys grabbed.  Only one of XGrabKey invocations failed, others might have been successfull. */
		ungrab_keyboard();
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grab_keyboard_checkbutton), keyboard_grabbed);

		return;
	}

	g_atexit(ungrab_keyboard);

	gdk_window_add_filter(NULL, keyboard_grab_filter, NULL);
}

#else /* ! HAVE_X11 */

void
ungrab_keyboard(void)
{
}

void
grab_keyboard(void)
{
	g_critical("Compiled without XGrabKey support; keyboard grabbing not possible.");
}

#endif /* ! HAVE_X11 */

void
grab_keyboard_handler(GtkToggleButton *togglebutton, gpointer notused)
{
	gboolean active = gtk_toggle_button_get_active(togglebutton);

	if (active)
		grab_keyboard();
	else
		ungrab_keyboard();
}

void
octave_event_handler(GtkSpinButton *spinbutton, gpointer notused)
{
	octave = gtk_spin_button_get_value(spinbutton);
	piano_keyboard_set_octave(keyboard, octave);
}

static void
panic(void)
{
	int i;

	/*
	 * These two have to be sent first, in case we have no room in the
	 * ringbuffer for all these MIDI_NOTE_OFF messages sent five lines below.
	 */
	queue_new_message(MIDI_CONTROLLER, MIDI_ALL_NOTES_OFF, 0);
	queue_new_message(MIDI_CONTROLLER, MIDI_ALL_SOUND_OFF, 0);

	for (i = 0; i < NNOTES; i++) {
		queue_new_message(MIDI_NOTE_OFF, i, 0);
		piano_keyboard_set_note_off(keyboard, i);
		usleep(100);
	}

	queue_new_message(MIDI_CONTROLLER, MIDI_HOLD_PEDAL, 0);
	queue_new_message(MIDI_CONTROLLER, MIDI_ALL_MIDI_CONTROLLERS_OFF, 0);
	queue_new_message(MIDI_CONTROLLER, MIDI_ALL_NOTES_OFF, 0);
	queue_new_message(MIDI_CONTROLLER, MIDI_ALL_SOUND_OFF, 0);
	queue_new_message(MIDI_RESET, -1, -1);
}

void
add_digit(int digit)
{
	if (entered_number == -1)
		entered_number = 0;
	else
		entered_number *= 10;

	entered_number += digit;
}

int
maybe_add_digit(GdkEventKey *event)
{
	/*
	 * User can enter a number from the keypad; after that, pressing
	 * '*'/'/', Page Up/Page Down etc will set program/bank/whatever
	 * to the number that was entered.
	 */
	/*
	 * XXX: This is silly.  Find a way to enter the number without
	 * all these contitional instructions.
	 */
	if (event->keyval == GDK_KP_0 || event->keyval == GDK_KP_Insert) {
		if (event->type == GDK_KEY_PRESS)
			add_digit(0);

		return (TRUE);
	}

	if (event->keyval == GDK_KP_1 || event->keyval == GDK_KP_End) {
		if (event->type == GDK_KEY_PRESS)
			add_digit(1);

		return (TRUE);
	}

	if (event->keyval == GDK_KP_2 || event->keyval == GDK_KP_Down) {
		if (event->type == GDK_KEY_PRESS)
			add_digit(2);

		return (TRUE);
	}

	if (event->keyval == GDK_KP_3 || event->keyval == GDK_KP_Page_Down) {
		if (event->type == GDK_KEY_PRESS)
			add_digit(3);

		return (TRUE);
	}

	if (event->keyval == GDK_KP_4 || event->keyval == GDK_KP_Left) {
		if (event->type == GDK_KEY_PRESS)
			add_digit(4);

		return (TRUE);
	}

	if (event->keyval == GDK_KP_5 || event->keyval == GDK_KP_Begin) {
		if (event->type == GDK_KEY_PRESS)
			add_digit(5);

		return (TRUE);
	}

	if (event->keyval == GDK_KP_6 || event->keyval == GDK_KP_Right) {
		if (event->type == GDK_KEY_PRESS)
			add_digit(6);

		return (TRUE);
	}

	if (event->keyval == GDK_KP_7 || event->keyval == GDK_KP_Home) {
		if (event->type == GDK_KEY_PRESS)
			add_digit(7);

		return (TRUE);
	}

	if (event->keyval == GDK_KP_8 || event->keyval == GDK_KP_Up) {
		if (event->type == GDK_KEY_PRESS)
			add_digit(8);

		return (TRUE);
	}

	if (event->keyval == GDK_KP_9 || event->keyval == GDK_KP_Page_Up) {
		if (event->type == GDK_KEY_PRESS)
			add_digit(9);

		return (TRUE);
	}

	return (FALSE);
}

int
get_entered_number(void)
{
	int tmp;

	tmp = entered_number;

	entered_number = -1;

	return (tmp);
}

int
clip(int val, int lo, int hi)
{
	if (val < lo)
		val = lo;

	if (val > hi)
		val = hi;

	return (val);
}

gint
keyboard_event_handler(GtkWidget *widget, GdkEventKey *event, gpointer notused)
{
	int tmp;
	gboolean retval = FALSE;

	/* Pass signal to piano_keyboard widget.  Is there a better way to do this? */
	if (event->type == GDK_KEY_PRESS)
		g_signal_emit_by_name(keyboard, "key-press-event", event, &retval);
	else
		g_signal_emit_by_name(keyboard, "key-release-event", event, &retval);

	if (retval)
		return (TRUE);


	if (maybe_add_digit(event))
		return (TRUE);

	/*
	 * '+' key shifts octave up. '-' key shifts octave down.
	 */
	if (event->keyval == GDK_KP_Add || event->keyval  == GDK_equal) {
		if (event->type == GDK_KEY_PRESS && octave < OCTAVE_MAX)
			gtk_spin_button_set_value(GTK_SPIN_BUTTON(octave_spin), octave + 1);

		return (TRUE);
	}

	if (event->keyval == GDK_KP_Subtract || event->keyval == GDK_minus) {
		if (event->type == GDK_KEY_PRESS && octave > OCTAVE_MIN)
			gtk_spin_button_set_value(GTK_SPIN_BUTTON(octave_spin), octave - 1);

		return (TRUE);
	}

	/*
	 * '*' character increases program number. '/' character decreases it.
	 */
	if (event->keyval == GDK_KP_Multiply) {
		if (event->type == GDK_KEY_PRESS) {

			tmp = get_entered_number();

			if (tmp < 0)
				tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(program_spin)) + 1;

			gtk_spin_button_set_value(GTK_SPIN_BUTTON(program_spin), clip(tmp, PROGRAM_MIN, PROGRAM_MAX));
		}

		return (TRUE);
	}

	if (event->keyval == GDK_KP_Divide) {
		if (event->type == GDK_KEY_PRESS) {

			tmp = get_entered_number();

			if (tmp < 0)
				tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(program_spin)) - 1;

			gtk_spin_button_set_value(GTK_SPIN_BUTTON(program_spin), clip(tmp, PROGRAM_MIN, PROGRAM_MAX));
		}

		return (TRUE);
	}

	/*
	 * PgUp key increases bank number, PgDown decreases it.
	 */
	if (event->keyval == GDK_Page_Up) {
		if (event->type == GDK_KEY_PRESS) {

			tmp = get_entered_number();

			if (tmp < 0)
				tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(bank_spin)) + 1;

			gtk_spin_button_set_value(GTK_SPIN_BUTTON(bank_spin), clip(tmp, BANK_MIN, BANK_MAX));
		}

		return (TRUE);
	}

	if (event->keyval == GDK_Page_Down) {
		if (event->type == GDK_KEY_PRESS) {

			tmp = get_entered_number();

			if (tmp < 0)
				tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(bank_spin)) - 1;

			gtk_spin_button_set_value(GTK_SPIN_BUTTON(bank_spin), clip(tmp, BANK_MIN, BANK_MAX));
		}

		return (TRUE);
	}

	/*
	 * Home key increases channel number, End decreases it.
	 */
	if (event->keyval == GDK_Home) {
		if (event->type == GDK_KEY_PRESS) {

			tmp = get_entered_number();

			if (tmp < 0)
				tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(channel_spin)) + 1;

			gtk_spin_button_set_value(GTK_SPIN_BUTTON(channel_spin), clip(tmp, CHANNEL_MIN, CHANNEL_MAX));
		}

		return (TRUE);
	}

	if (event->keyval == GDK_End) {
		if (event->type == GDK_KEY_PRESS) {

			tmp = get_entered_number();

			if (tmp < 0)
				tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(channel_spin)) - 1;

			gtk_spin_button_set_value(GTK_SPIN_BUTTON(channel_spin), clip(tmp, CHANNEL_MIN, CHANNEL_MAX));
		}

		return (TRUE);
	}

	/*
	 * Insert key connects to the next input port.  Delete connects to the previous one.
	 */
	if (event->keyval == GDK_Insert) {
		if (event->type == GDK_KEY_PRESS)
			connect_to_next_input_port();

		return (TRUE);
	}

	if (event->keyval == GDK_Delete) {
		if (event->type == GDK_KEY_PRESS)
			connect_to_prev_input_port();

		return (TRUE);
	}

	if (event->keyval == GDK_Escape) {
		if (event->type == GDK_KEY_PRESS)
			panic();

		return (TRUE);
	}

	/*
	 * Spacebar works as a 'sustain' key.  Holding spacebar while
	 * releasing note will cause the note to continue. Pressing and
	 * releasing spacebar without pressing any note keys will make all
	 * the sustained notes end (it will send 'note off' midi messages for
	 * all the sustained notes).
	 */
	if (event->keyval == GDK_space) {
		if (event->type == GDK_KEY_PRESS)
			gtk_button_pressed(GTK_BUTTON(sustain_button));
		else
			gtk_button_released(GTK_BUTTON(sustain_button));

		return (TRUE);
	}

	/*
	 * Shift increases velocity, i.e. holding it while pressing note key
	 * will make the sound louder. Ctrl decreases velocity.
	 */
	if (event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R) {
		if (event->type == GDK_KEY_PRESS)
			current_velocity = &velocity_high;
		else
			current_velocity = &velocity_normal;

		gtk_range_set_value(GTK_RANGE(velocity_scale), *current_velocity);

		return (TRUE);

	}

	if (event->keyval == GDK_Control_L || event->keyval == GDK_Control_R) {
		if (event->type == GDK_KEY_PRESS)
			current_velocity = &velocity_low;
		else
			current_velocity = &velocity_normal;

		gtk_range_set_value(GTK_RANGE(velocity_scale), *current_velocity);

		return (TRUE);
	}

	return (FALSE);
}

void
note_on_event_handler(GtkWidget *widget, int note)
{
	assert(current_velocity);

	queue_new_message(MIDI_NOTE_ON, note, *current_velocity);
}

void
note_off_event_handler(GtkWidget *widget, int note)
{
	assert(current_velocity);

	queue_new_message(MIDI_NOTE_OFF, note, *current_velocity);
}

void
init_gtk_1(int *argc, char ***argv)
{
	GdkPixbuf *icon = NULL;
	GError *error = NULL;

	gtk_init(argc, argv);

	icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), "jack-keyboard", 48, 0, &error);

	if (icon == NULL) {
		fprintf(stderr, "%s: Cannot load icon: %s.\n", G_LOG_DOMAIN, error->message);
		g_error_free(error);

	} else {
		gtk_window_set_default_icon(icon);
	}

	/* Window. */
	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_container_set_border_width(GTK_CONTAINER(window), 2);
	
	gtk_rc_parse_string (rc_style);
}

void
init_gtk_2(void)
{
	keyboard = PIANO_KEYBOARD(piano_keyboard_new());
	
	if (!enable_gui) {
		gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(keyboard));
		return;
	}
	
	GtkWidget *label;
	
	/* Main Table. */
	GtkTable *maintable = GTK_TABLE(gtk_table_new(3, 4, FALSE));
	gtk_table_set_row_spacings(maintable, 5);
	gtk_table_set_col_spacings(maintable, 5);
	gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(maintable));
	
	
	/* Num Table. */
	GtkWidget *numwidget = gtk_table_new(2, 4, FALSE);
	gtk_table_set_row_spacings(GTK_TABLE(numwidget), 5);
	gtk_table_set_col_spacings(GTK_TABLE(numwidget), 5);
	gtk_table_attach(maintable, numwidget, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);

	/* Channel label and spin. */
	label = gtk_label_new("Chan");
	gtk_table_attach(GTK_TABLE(numwidget), label, 0, 1, 0, 1,GTK_FILL, GTK_FILL, 0, 0);
	channel_spin = gtk_spin_button_new_with_range(1, CHANNEL_MAX, 1);
	GTK_WIDGET_UNSET_FLAGS(channel_spin, GTK_CAN_FOCUS);
	gtk_table_attach(GTK_TABLE(numwidget), channel_spin, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(channel_spin), "value-changed", G_CALLBACK(channel_event_handler), NULL);
	
	/* Octave label and spin. */
	label = gtk_label_new("Oct");
	gtk_table_attach(GTK_TABLE(numwidget), label, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
	octave_spin = gtk_spin_button_new_with_range(OCTAVE_MIN, OCTAVE_MAX, 1);
	GTK_WIDGET_UNSET_FLAGS(octave_spin, GTK_CAN_FOCUS);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(octave_spin), octave);
	gtk_table_attach(GTK_TABLE(numwidget), octave_spin, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(octave_spin), "value-changed", G_CALLBACK(octave_event_handler), NULL);
	
	/* Bank label and spin. */
	label = gtk_label_new("Bank");
	gtk_table_attach(GTK_TABLE(numwidget), label, 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
	bank_spin = gtk_spin_button_new_with_range(0, BANK_MAX, 1);
	GTK_WIDGET_UNSET_FLAGS(bank_spin, GTK_CAN_FOCUS);
	gtk_table_attach(GTK_TABLE(numwidget), bank_spin, 2, 3, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(bank_spin), "value-changed", G_CALLBACK(bank_event_handler), NULL);

	/* Program label and spin. */
	label = gtk_label_new("Prog");
	gtk_table_attach(GTK_TABLE(numwidget), label, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
	program_spin = gtk_spin_button_new_with_range(0, PROGRAM_MAX, 1);
	GTK_WIDGET_UNSET_FLAGS(program_spin, GTK_CAN_FOCUS);
	gtk_table_attach(GTK_TABLE(numwidget), program_spin, 3, 4, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(program_spin), "value-changed", G_CALLBACK(program_event_handler), NULL);
	
	/* PANIC */
	panic_button = gtk_button_new_with_label("    PANIC    ");
	gtk_button_set_focus_on_click(GTK_BUTTON(panic_button), FALSE);
	gtk_table_attach(maintable, panic_button, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(panic_button), "pressed", G_CALLBACK(panic_event_handler), NULL);
	
	/* "Connected to" label and combo box. */
	GtkCellRenderer *renderer;
	label = gtk_label_new("Connect");
	gtk_table_attach(GTK_TABLE(numwidget), label, 4, 5, 0, 1, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);

	connected_to_store = gtk_list_store_new(1, G_TYPE_STRING);
	connected_to_combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(connected_to_store));

	renderer = gtk_cell_renderer_text_new ();
	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(connected_to_combo), renderer, FALSE);
	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT(connected_to_combo), renderer, "text", 0, NULL);

	GTK_WIDGET_UNSET_FLAGS(connected_to_combo, GTK_CAN_FOCUS);
	gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(connected_to_combo), FALSE);
	gtk_table_attach(GTK_TABLE(numwidget), connected_to_combo, 4, 5, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
	gtk_widget_set_size_request(GTK_WIDGET(connected_to_combo), 160, -1);
	g_signal_connect(G_OBJECT(connected_to_combo), "changed", G_CALLBACK(connected_to_event_handler), NULL);

	/* "Grab keyboard" label and checkbutton. */
	label = gtk_label_new("Grab Keyboard");
	gtk_table_attach(GTK_TABLE(numwidget), label, 5, 6, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
	grab_keyboard_checkbutton = gtk_check_button_new();
	GTK_WIDGET_UNSET_FLAGS(grab_keyboard_checkbutton, GTK_CAN_FOCUS);
	g_signal_connect(G_OBJECT(grab_keyboard_checkbutton), "toggled", G_CALLBACK(grab_keyboard_handler), NULL);
	gtk_table_attach(GTK_TABLE(numwidget), grab_keyboard_checkbutton, 5, 6, 1, 2, GTK_FILL, GTK_FILL, 0, 0);

	/* Sustain. It's a toggle button, not an ordinary one, because we want gtk_whatever_set_active() to work.*/
	sustain_button = gtk_toggle_button_new_with_label("Sustain");
	gtk_button_set_focus_on_click(GTK_BUTTON(sustain_button), FALSE);
	gtk_table_attach(maintable, sustain_button, 0, 3, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(sustain_button), "pressed", G_CALLBACK(sustain_event_handler), (void *)1);
	g_signal_connect(G_OBJECT(sustain_button), "released", G_CALLBACK(sustain_event_handler), (void *)0);

	/* Mod Table. */
	GtkWidget *modwidget = gtk_table_new(2, 3, FALSE);
	gtk_table_set_row_spacings(GTK_TABLE(modwidget), 2);
	gtk_table_set_col_spacings(GTK_TABLE(modwidget), 5);
	gtk_table_attach(maintable, modwidget, 3, 4, 0, 3, GTK_FILL, GTK_FILL, 0, 0);
	
	/* Velocity label and hscale */
	label = gtk_label_new("Vel");
	gtk_table_attach(GTK_TABLE(modwidget), label, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0);

	velocity_scale = gtk_vscale_new_with_range(VELOCITY_MIN, VELOCITY_MAX, 1);
	gtk_scale_set_value_pos(GTK_SCALE(velocity_scale), GTK_POS_BOTTOM);
	GTK_WIDGET_UNSET_FLAGS(velocity_scale, GTK_CAN_FOCUS);
	g_signal_connect(G_OBJECT(velocity_scale), "value-changed", G_CALLBACK(velocity_event_handler), NULL);
	gtk_range_set_value(GTK_RANGE(velocity_scale), VELOCITY_NORMAL);
	gtk_range_set_inverted(GTK_RANGE(velocity_scale), TRUE);
	gtk_range_set_round_digits(GTK_RANGE(velocity_scale), 0);
	gtk_table_attach(GTK_TABLE(modwidget), velocity_scale, 0, 1, 1, 2, GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_set_size_request(GTK_WIDGET(velocity_scale), 40, 160);
	
	/* Modulation label and vscale */
	label = gtk_label_new("Mod");
	gtk_table_attach(GTK_TABLE(modwidget), label, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);

	mod_scale = gtk_vscale_new_with_range(MOD_MIN, MOD_MAX, 1);
	gtk_scale_set_value_pos(GTK_SCALE(mod_scale), GTK_POS_BOTTOM);
	GTK_WIDGET_UNSET_FLAGS(mod_scale, GTK_CAN_FOCUS);
	gtk_range_set_value(GTK_RANGE(mod_scale), MOD_INIT);
	gtk_range_set_inverted(GTK_RANGE(mod_scale), TRUE);
	gtk_range_set_round_digits(GTK_RANGE(mod_scale), 0);
	g_signal_connect(G_OBJECT(mod_scale), "value-changed", G_CALLBACK(mod_event_handler), NULL);
	gtk_table_attach(GTK_TABLE(modwidget), mod_scale, 1, 2, 1, 2, GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_set_size_request(GTK_WIDGET(mod_scale), 40, 160);
	
	/* Pitch Bend label and vscale */
	label = gtk_label_new("Pitch");
	gtk_table_attach(GTK_TABLE(modwidget), label, 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0);

	pitch_scale = gtk_vscale_new_with_range(PITCH_MIN, PITCH_MAX, 1);
	gtk_scale_set_value_pos(GTK_SCALE(pitch_scale), GTK_POS_BOTTOM);
	GTK_WIDGET_UNSET_FLAGS(pitch_scale, GTK_CAN_FOCUS);
	gtk_range_set_value(GTK_RANGE(pitch_scale), PITCH_INIT);
	gtk_range_set_inverted(GTK_RANGE(pitch_scale), TRUE);
	gtk_range_set_round_digits(GTK_RANGE(pitch_scale), 0);
	g_signal_connect(G_OBJECT(pitch_scale), "value-changed", G_CALLBACK(pitch_event_handler), NULL);
	gtk_table_attach(GTK_TABLE(modwidget), pitch_scale, 2, 3, 1, 2, GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_set_size_request(GTK_WIDGET(pitch_scale), 40, 160);
	
	
	/* Keyboard */
	gtk_table_attach(maintable, GTK_WIDGET(keyboard), 0, 3, 2, 3, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	
	
	g_signal_connect(G_OBJECT(keyboard), "note-on", G_CALLBACK(note_on_event_handler), NULL);
	g_signal_connect(G_OBJECT(keyboard), "note-off", G_CALLBACK(note_off_event_handler), NULL);
	g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
	g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(keyboard_event_handler), NULL);
	g_signal_connect(G_OBJECT(window), "key-release-event", G_CALLBACK(keyboard_event_handler), NULL);
	gtk_widget_show_all(window);

	draw_window_title();
}

void
log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer notused)
{
	GtkWidget *dialog;

	fprintf(stderr, "%s: %s\n", log_domain, message);

	if ((log_level | G_LOG_LEVEL_CRITICAL) == G_LOG_LEVEL_CRITICAL) {
		dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
			GTK_BUTTONS_CLOSE, "%s", message);

		gtk_dialog_run(GTK_DIALOG(dialog));

		gtk_widget_destroy(dialog);
	}
}

void
show_version(void)
{
	fprintf(stdout, "%s\n", PACKAGE_NAME " v" PACKAGE_VERSION);

	exit(EX_OK);
}

void
usage(void)
{
	fprintf(stderr, "usage: jack-keyboard [-CGKTVkturf] [ -a <input port>] [-c <channel>] [-b <bank> ] [-p <program>] [-l <layout>]\n");
	fprintf(stderr, "   where <channel> is MIDI channel to use for output, from 1 to 16,\n");
	fprintf(stderr, "   <bank> is MIDI bank to use, from 0 to 16383,\n");
	fprintf(stderr, "   <program> is MIDI program to use, from 0 to 127,\n");
	fprintf(stderr, "   and <layout> is QWERTY.\n");
	fprintf(stderr, "See manual page for details.\n");

	exit(EX_USAGE);
}

int
main(int argc, char *argv[])
{
	int ch, enable_keyboard_cue = 0, initial_channel = 1, initial_bank = 0, initial_program = 0, full_midi_keyboard = 0;
	char *keyboard_layout = NULL, *autoconnect_port_name = NULL;

#ifdef HAVE_LASH
	lash_args_t *lash_args;
#endif

	g_thread_init(NULL);

#ifdef HAVE_LASH
	lash_args = lash_extract_args(&argc, &argv);
#endif

	init_gtk_1(&argc, &argv);

	g_log_set_default_handler(log_handler, NULL);

	while ((ch = getopt(argc, argv, "CGKTVa:nktur:c:b:p:l:f")) != -1) {
		switch (ch) {
		case 'C':
			enable_keyboard_cue = 1;
			break;

		case 'G':
			enable_gui = 0;
			enable_window_title = !enable_window_title;
			break;

		case 'K':
			grab_keyboard_at_startup = 1;
			break;

		case 'T':
			enable_window_title = !enable_window_title;
			break;

		case 'V':
			show_version();
			break;

		case 'a':
			autoconnect_port_name = strdup(optarg);
			break;

		case 'n':
			/* Do nothing; backward compatibility. */
			break;

		case 'k':
			allow_connecting_to_own_kind = 1;
			break;

		case 'l':
			keyboard_layout = strdup(optarg);
			break;

		case 't':
			time_offsets_are_zero = 1;
			break;

		case 'u':
			send_program_change_at_reconnect = 1;
			break;

		case 'c':
			initial_channel = atoi(optarg);

			if (initial_channel < CHANNEL_MIN || initial_channel > CHANNEL_MAX) {
				g_critical("Invalid MIDI channel number specified on the command line; "
					"valid values are %d-%d.", CHANNEL_MIN, CHANNEL_MAX);

				exit(EX_USAGE);
			}

			break;

		case 'b':
			initial_bank = atoi(optarg);

			send_program_change_once = 1;

			if (initial_bank < BANK_MIN || initial_bank > BANK_MAX) {
				g_critical("Invalid MIDI bank number specified on the command line; "
					"valid values are %d-%d.", BANK_MIN, BANK_MAX);

				exit(EX_USAGE);
			}

			break;

		case 'p':
			initial_program = atoi(optarg);

			send_program_change_once = 1;

			if (initial_program < PROGRAM_MIN || initial_program > PROGRAM_MAX) {
				g_critical("Invalid MIDI program number specified on the command line; "
					"valid values are %d-%d.", PROGRAM_MIN, PROGRAM_MAX);

				exit(EX_USAGE);
			}

			break;

		case 'r':
			rate_limit = strtod(optarg, NULL);
			if (rate_limit <= 0.0) {
				g_critical("Invalid rate limit specified.\n");

				exit(EX_USAGE);
			}

			break;

		case 'f':
			full_midi_keyboard = 1;
			break;

		case '?':
		default:
			usage();
		}
	}

	argc -= optind;
	argv += optind;

	init_gtk_2();

	if (full_midi_keyboard)
		piano_keyboard_enable_all_midi_notes(keyboard);

	if (keyboard_layout != NULL) {
		int ret = piano_keyboard_set_keyboard_layout(keyboard, keyboard_layout);

		if (ret) {
			g_critical("Invalid layout, proper choices are QWERTY.");
			exit(EX_USAGE);
		}
	}

#ifdef HAVE_LASH
	init_lash(lash_args);
#endif

	init_jack();

	if (autoconnect_port_name) {
		if (connect_to_input_port(autoconnect_port_name)) {
			g_critical("Couldn't connect to '%s', exiting.", autoconnect_port_name);
			exit(EX_UNAVAILABLE);
		}
	}

	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grab_keyboard_checkbutton), grab_keyboard_at_startup);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(channel_spin), initial_channel);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(bank_spin), initial_bank);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(program_spin), initial_program);
	piano_keyboard_set_keyboard_cue(keyboard, enable_keyboard_cue);

	gtk_main();

	return (0);
}

