/**
 * SDL interface
 */

#ifdef __MINGW32__
#undef __STRICT_ANSI__
#endif

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include <SDL.h>
#include <SDL_audio.h>

#ifdef WITH_OPENGL
# include <SDL_opengl.h>
#endif

#ifdef WITH_THREADS
#include <SDL_thread.h>
#endif

#ifdef HAVE_MEMCPY_H
#include "memcpy.h"
#endif
#include "md.h"
#include "rc.h"
#include "rc-vars.h"
#include "pd.h"
#include "pd-defs.h"
#include "font.h"
#include "system.h"
#include "prompt.h"
#include "romload.h"
#include "splash.h"

#ifdef WITH_HQX
#define HQX_NO_UINT24
#include "hqx.h"
#endif

#ifdef WITH_SCALE2X
extern "C" {
#include "scalebit.h"
}
#endif

/// Number of microseconds to sustain messages
#define MESSAGE_LIFE 3000000

static void pd_message_process(void);
static size_t pd_message_write(const char *msg, size_t len, unsigned int mark);
static size_t pd_message_display(const char *msg, size_t len,
				 unsigned int mark, bool update);
static void pd_message_postpone(const char *msg);
static void pd_message_cursor(unsigned int mark, const char *msg, ...);

/// Generic type for supported colour depths.
typedef union {
	uint8_t *u8;
	uint32_t *u32;
	uint24_t *u24;
	uint16_t *u16;
	uint16_t *u15;
} bpp_t;

#ifdef WITH_OPENGL

/// Framebuffer texture.
struct texture {
	unsigned int width; ///< texture width
	unsigned int height; ///< texture height
	unsigned int vis_width; ///< visible width
	unsigned int vis_height; ///< visible height
	GLuint id; ///< texture identifier
	GLuint dlist; ///< display list
	unsigned int u32:1; ///< texture is 32-bit
	unsigned int linear:1; ///< linear filtering is enabled
	union {
		uint16_t *u16;
		uint32_t *u32;
	} buf; ///< 16 or 32-bit buffer
};

static void release_texture(struct texture&);
static int init_texture(struct screen *);
static void update_texture(struct texture&);

#endif // WITH_OPENGL

struct screen {
	unsigned int window_width; ///< window width
	unsigned int window_height; ///< window height
	unsigned int width; ///< buffer width
	unsigned int height; ///< buffer height
	unsigned int bpp; ///< bits per pixel
	unsigned int Bpp; ///< bytes per pixel
	unsigned int x_scale; ///< horizontal scale factor
	unsigned int y_scale; ///< vertical scale factor
	unsigned int info_height; ///< message bar height (included in height)
	bpp_t buf; ///< generic pointer to pixel data
	unsigned int pitch; ///< number of bytes per line in buf
	SDL_Surface *surface; ///< SDL surface
	unsigned int want_fullscreen:1; ///< want fullscreen
	unsigned int is_fullscreen:1; ///< fullscreen enabled
#ifdef WITH_OPENGL
	struct texture texture; ///< OpenGL texture data
	unsigned int want_opengl:1; ///< want OpenGL
	unsigned int is_opengl:1; ///< OpenGL enabled
#endif
#ifdef WITH_THREADS
	unsigned int want_thread:1; ///< want updates from a separate thread
	unsigned int is_thread:1; ///< thread is present
	SDL_Thread *thread; ///< thread itself
	SDL_mutex *lock; ///< lock for updates
	SDL_cond *cond; ///< condition variable to signal updates
#endif
	SDL_Color color[64]; ///< SDL colors for 8bpp modes
};

static struct screen screen;

static struct {
	const unsigned int width; ///< 320
	unsigned int height; ///< 224 or 240 (NTSC_VBLANK or PAL_VBLANK)
	unsigned int hz; ///< refresh rate
	unsigned int is_pal: 1; ///< PAL enabled
	uint8_t palette[256]; ///< palette for 8bpp modes (mdpal)
} video = {
	320, ///< width is always 320
	NTSC_VBLANK, ///< NTSC height by default
	NTSC_HZ, ///< 60Hz
	0, ///< NTSC is enabled
	{ 0 }
};

/**
 * Call this before accessing screen.buf.
 * No syscalls allowed before screen_unlock().
 */
static int screen_lock()
{
#ifdef WITH_THREADS
	if (screen.is_thread) {
		assert(screen.lock != NULL);
		SDL_LockMutex(screen.lock);
	}
#endif
#ifdef WITH_OPENGL
	if (screen.is_opengl)
		return 0;
#endif
	if (SDL_MUSTLOCK(screen.surface) == 0)
		return 0;
	return SDL_LockSurface(screen.surface);
}

/**
 * Call this after accessing screen.buf.
 */
static void screen_unlock()
{
#ifdef WITH_THREADS
	if (screen.is_thread) {
		assert(screen.lock != NULL);
		SDL_UnlockMutex(screen.lock);
	}
#endif
#ifdef WITH_OPENGL
	if (screen.is_opengl)
		return;
#endif
	if (SDL_MUSTLOCK(screen.surface) == 0)
		return;
	SDL_UnlockSurface(screen.surface);
}

/**
 * Do not call this directly, use screen_update() instead.
 */
static void screen_update_once()
{
#ifdef WITH_OPENGL
	if (screen.is_opengl) {
		update_texture(screen.texture);
		return;
	}
#endif
	SDL_Flip(screen.surface);
}

#ifdef WITH_THREADS

static int screen_update_thread(void *)
{
	assert(screen.lock != NULL);
	assert(screen.cond != NULL);
	SDL_LockMutex(screen.lock);
	while (screen.want_thread) {
		SDL_CondWait(screen.cond, screen.lock);
		screen_update_once();
	}
	SDL_UnlockMutex(screen.lock);
	return 0;
}

static void screen_update_thread_start()
{
	DEBUG(("starting thread..."));
	assert(screen.want_thread);
	assert(screen.lock == NULL);
	assert(screen.cond == NULL);
	assert(screen.thread == NULL);
#ifdef WITH_OPENGL
	if (screen.is_opengl) {
		DEBUG(("this is not supported when OpenGL is enabled"));
		return;
	}
#endif
	if ((screen.lock = SDL_CreateMutex()) == NULL) {
		DEBUG(("unable to create lock"));
		goto error;
	}

	if ((screen.cond = SDL_CreateCond()) == NULL) {
		DEBUG(("unable to create condition variable"));
		goto error;
	}
	screen.thread = SDL_CreateThread(screen_update_thread, NULL);
	if (screen.thread == NULL) {
		DEBUG(("unable to start thread"));
		goto error;
	}
	screen.is_thread = 1;
	DEBUG(("thread started"));
	return;
error:
	if (screen.cond != NULL) {
		SDL_DestroyCond(screen.cond);
		screen.cond = NULL;
	}
	if (screen.lock != NULL) {
		SDL_DestroyMutex(screen.lock);
		screen.lock = NULL;
	}
}

static void screen_update_thread_stop()
{
	if (!screen.is_thread) {
		assert(screen.thread == NULL);
		return;
	}
	DEBUG(("stopping thread..."));
	assert(screen.thread != NULL);
	screen.want_thread = 0;
	SDL_CondSignal(screen.cond);
	SDL_WaitThread(screen.thread, NULL);
	screen.thread = NULL;
	SDL_DestroyCond(screen.cond);
	screen.cond = NULL;
	SDL_DestroyMutex(screen.lock);
	screen.lock = NULL;
	screen.is_thread = 0;
	DEBUG(("thread stopped"));
}

#endif // WITH_THREADS

/**
 * Call this after writing into screen.buf.
 */
static void screen_update()
{
#ifdef WITH_THREADS
	if (screen.is_thread)
		SDL_CondSignal(screen.cond);
	else
#endif // WITH_THREADS
		screen_update_once();
}

/**
 * Clear screen.
 */
static void screen_clear()
{
	if ((screen.buf.u8 == NULL) || (screen_lock()))
		return;
	memset(screen.buf.u8, 0, (screen.pitch * screen.height));
	screen_unlock();
}

// Bad hack- extern slot etc. from main.cpp so we can save/load states
extern int slot;
void md_save(md &megad);
void md_load(md &megad);

// Define externed variables
struct bmap mdscr;
unsigned char *mdpal = NULL;
struct sndinfo sndi;
const char *pd_options =
#ifdef WITH_OPENGL
	"g:"
#endif
	"fX:Y:S:G:";

static void mdscr_splash();

/// Circular buffer and related functions.
typedef struct {
	size_t i; ///< data start index
	size_t s; ///< data size
	size_t size; ///< buffer size
	union {
		uint8_t *u8;
		int16_t *i16;
	} data; ///< storage
} cbuf_t;

/**
 * Write/copy data into a circular buffer.
 * @param[in,out] cbuf Destination buffer.
 * @param[in] src Buffer to copy from.
 * @param size Size of src.
 * @return Number of bytes copied.
 */
size_t cbuf_write(cbuf_t *cbuf, uint8_t *src, size_t size)
{
	size_t j;
	size_t k;

	if (size > cbuf->size) {
		src += (size - cbuf->size);
		size = cbuf->size;
	}
	k = (cbuf->size - cbuf->s);
	j = ((cbuf->i + cbuf->s) % cbuf->size);
	if (size > k) {
		cbuf->i = ((cbuf->i + (size - k)) % cbuf->size);
		cbuf->s = cbuf->size;
	}
	else
		cbuf->s += size;
	k = (cbuf->size - j);
	if (k >= size) {
		memcpy(&cbuf->data.u8[j], src, size);
	}
	else {
		memcpy(&cbuf->data.u8[j], src, k);
		memcpy(&cbuf->data.u8[0], &src[k], (size - k));
	}
	return size;
}

/**
 * Read bytes out of a circular buffer.
 * @param[out] dst Destination buffer.
 * @param[in,out] cbuf Circular buffer to read from.
 * @param size Maximum number of bytes to copy to dst.
 * @return Number of bytes copied.
 */
size_t cbuf_read(uint8_t *dst, cbuf_t *cbuf, size_t size)
{
	if (size > cbuf->s)
		size = cbuf->s;
	if ((cbuf->i + size) > cbuf->size) {
		size_t k = (cbuf->size - cbuf->i);

		memcpy(&dst[0], &cbuf->data.u8[(cbuf->i)], k);
		memcpy(&dst[k], &cbuf->data.u8[0], (size - k));
	}
	else
		memcpy(&dst[0], &cbuf->data.u8[(cbuf->i)], size);
	cbuf->i = ((cbuf->i + size) % cbuf->size);
	cbuf->s -= size;
	return size;
}

/// Sound
static struct {
	unsigned int rate; ///< samples rate
	unsigned int samples; ///< number of samples required by the callback
	cbuf_t cbuf; ///< circular buffer
} sound;

/// Messages
static struct {
	unsigned int displayed:1; ///< whether message is currently displayed
	unsigned long since; ///< since this number of microseconds
	size_t length; ///< remaining length to display
	char message[2048]; ///< message
} info;

/// Prompt
static struct {
	struct prompt status; ///< prompt status
	char** complete; ///< completion results array
	unsigned int skip; ///< number of entries to skip in the array
	unsigned int common; ///< common length of all entries
} prompt;

/// Prompt return values
#define PROMPT_RET_CONT 0x01 ///< waiting for more input
#define PROMPT_RET_EXIT 0x02 ///< leave prompt normally
#define PROMPT_RET_ERROR 0x04 ///< leave prompt with error
#define PROMPT_RET_ENTER 0x10 ///< previous line entered
#define PROMPT_RET_MSG 0x80 ///< pd_message() has been used

struct prompt_command {
	const char* name;
	/// command function pointer
        int (*cmd)(class md&, unsigned int, const char**);
	/// completion function shoud complete the last entry in the array
	char* (*cmpl)(class md&, unsigned int, const char**, unsigned int);
};

// Extra commands usable from prompt.
static int prompt_cmd_exit(class md&, unsigned int, const char**);
static int prompt_cmd_load(class md&, unsigned int, const char**);
static char* prompt_cmpl_load(class md&, unsigned int, const char**,
			      unsigned int);
static int prompt_cmd_unload(class md&, unsigned int, const char**);
static int prompt_cmd_reset(class md&, unsigned int, const char**);
static int prompt_cmd_unbind(class md&, unsigned int, const char**);
static char* prompt_cmpl_unbind(class md&, unsigned int, const char**,
				unsigned int);
#ifdef WITH_CTV
static int prompt_cmd_filter_push(class md&, unsigned int, const char**);
static char* prompt_cmpl_filter_push(class md&, unsigned int, const char**,
				     unsigned int);
static int prompt_cmd_filter_pop(class md&, unsigned int, const char**);
static int prompt_cmd_filter_none(class md&, unsigned int, const char**);
#endif
static int prompt_cmd_calibrate(class md&, unsigned int, const char**);
static int prompt_cmd_config_load(class md&, unsigned int, const char**);
static int prompt_cmd_config_save(class md&, unsigned int, const char**);
static char* prompt_cmpl_config_file(class md&, unsigned int, const char**,
				     unsigned int);
#ifdef WITH_VGMDUMP
static char* prompt_cmpl_vgmdump(class md&, unsigned int, const char**,
				 unsigned int);
static int prompt_cmd_vgmdump(class md&, unsigned int, const char**);
#endif

/**
 * List of commands to auto complete.
 */
static const struct prompt_command prompt_command[] = {
	{ "quit", prompt_cmd_exit, NULL },
	{ "q", prompt_cmd_exit, NULL },
	{ "exit", prompt_cmd_exit, NULL },
	{ "load", prompt_cmd_load, prompt_cmpl_load },
	{ "open", prompt_cmd_load, prompt_cmpl_load },
	{ "o", prompt_cmd_load, prompt_cmpl_load },
	{ "plug", prompt_cmd_load, prompt_cmpl_load },
	{ "unload", prompt_cmd_unload, NULL },
	{ "close", prompt_cmd_unload, NULL },
	{ "unplug", prompt_cmd_unload, NULL },
	{ "reset", prompt_cmd_reset, NULL },
	{ "unbind", prompt_cmd_unbind, prompt_cmpl_unbind },
#ifdef WITH_CTV
	{ "ctv_push", prompt_cmd_filter_push, prompt_cmpl_filter_push },
	{ "ctv_pop", prompt_cmd_filter_pop, NULL },
	{ "ctv_none", prompt_cmd_filter_none, NULL },
#endif
	{ "calibrate", prompt_cmd_calibrate, NULL },
	{ "calibrate_js", prompt_cmd_calibrate, NULL }, // deprecated name
	{ "config_load", prompt_cmd_config_load, prompt_cmpl_config_file },
	{ "config_save", prompt_cmd_config_save, prompt_cmpl_config_file },
#ifdef WITH_VGMDUMP
	{ "vgmdump", prompt_cmd_vgmdump, prompt_cmpl_vgmdump },
#endif
	{ NULL, NULL, NULL }
};

/// Extra commands return values.
#define CMD_OK 0x00 ///< command successful
#define CMD_EINVAL 0x01 ///< invalid argument
#define CMD_FAIL 0x02 ///< command failed
#define CMD_ERROR 0x03 ///< fatal error, DGen should exit
#define CMD_MSG 0x80 ///< pd_message() has been used

/// Stopped flag used by pd_stopped()
static int stopped = 0;

/// Events handling status.
static enum events {
	STARTED,
	STOPPED,
	STOPPED_PROMPT,
	STOPPED_GAME_GENIE,
	PROMPT,
	GAME_GENIE
} events = STARTED;

static int stop_events(md& megad, enum events status);
static void restart_events(md &megad);

/// Messages shown whenever events are stopped.
static const char stopped_str[] = "STOPPED.";
static const char prompt_str[] = ":";
static const char game_genie_str[] = "Enter Game Genie/Hex code: ";

/// Enable emulation by default.
bool pd_freeze = false;
static unsigned int pd_freeze_ref = 0;

static void freeze(bool toggle)
{
	if (toggle == true) {
		if (!pd_freeze_ref) {
			assert(pd_freeze == false);
			pd_freeze = true;
		}
		pd_freeze_ref++;
	}
	else {
		if (pd_freeze_ref) {
			assert(pd_freeze == true);
			pd_freeze_ref--;
			if (!pd_freeze_ref)
				pd_freeze = false;
		}
		else
			assert(pd_freeze == false);
	}
}

/**
 * Elapsed time in microseconds.
 * @return Microseconds.
 */
unsigned long pd_usecs(void)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);
	return (unsigned long)((tv.tv_sec * 1000000) + tv.tv_usec);
}

/**
 * Prompt "exit" command handler.
 * @return Error status to make DGen exit.
 */
static int prompt_cmd_exit(class md&, unsigned int, const char**)
{
	return (CMD_ERROR | CMD_MSG);
}

/**
 * Prompt "load" command handler.
 * @param md Context.
 * @param ac Number of arguments in av.
 * @param av Arguments.
 * @return Status code.
 */
static int prompt_cmd_load(class md& md, unsigned int ac, const char** av)
{
	extern int slot;
	extern void ram_save(class md&);
	extern void ram_load(class md&);
	char *s;

	if (ac != 2)
		return CMD_EINVAL;
	s = backslashify((const uint8_t *)av[1], strlen(av[1]), 0, NULL);
	if (s == NULL)
		return CMD_FAIL;
	ram_save(md);
	if (dgen_autosave) {
		slot = 0;
		md_save(md);
	}
	md.unplug();
	pd_message("");
	if (md.load(av[1])) {
		mdscr_splash();
		pd_message("Unable to load \"%s\"", s);
		free(s);
		return (CMD_FAIL | CMD_MSG);
	}
	pd_message("Loaded \"%s\"", s);
	free(s);
	if (dgen_show_carthead)
		pd_show_carthead(md);
	// Initialize like main() does.
	md.reset();

	if (!dgen_region) {
		uint8_t c = md.region_guess();
		int hz;
		int pal;

		md::region_info(c, &pal, &hz, 0, 0, 0);
		if ((hz != dgen_hz) || (pal != dgen_pal) || (c != md.region)) {
			md.region = c;
			dgen_hz = hz;
			dgen_pal = pal;
			printf("sdl: reconfiguring for region \"%c\": "
			       "%dHz (%s)\n", c, hz, (pal ? "PAL" : "NTSC"));
			pd_graphics_reinit(dgen_sound, dgen_pal, dgen_hz);
			if (dgen_sound) {
				long rate = dgen_soundrate;
				unsigned int samples;

				pd_sound_deinit();
				samples = (dgen_soundsegs * (rate / dgen_hz));
				pd_sound_init(rate, samples);
			}
			md.pal = pal;
			md.init_pal();
			md.init_sound();
		}
	}

	ram_load(md);
	if (dgen_autoload) {
		slot = 0;
		md_load(md);
	}
	return (CMD_OK | CMD_MSG);
}

static void rehash_prompt_complete_common()
{
	size_t i;
	unsigned int common;

	prompt.common = 0;
	if ((prompt.complete == NULL) || (prompt.complete[0] == NULL))
		return;
	common = strlen(prompt.complete[0]);
	for (i = 1; (prompt.complete[i] != NULL); ++i) {
		unsigned int tmp;

		tmp = strcommon(prompt.complete[i], prompt.complete[(i - 1)]);
		if (tmp < common)
			common = tmp;
		if (common == 0)
			break;
	}
	prompt.common = common;
}

static char* prompt_cmpl_load(class md& md, unsigned int ac, const char** av,
			      unsigned int len)
{
	const char *prefix;
	size_t i;
	unsigned int skip;

	(void)md;
	assert(ac != 0);
	if ((ac == 1) || (len == ~0u) || (av[(ac - 1)] == NULL)) {
		prefix = "";
		len = 0;
	}
	else
		prefix = av[(ac - 1)];
	if (prompt.complete == NULL) {
		// Rebuild cache.
		prompt.skip = 0;
		prompt.complete = complete_path(prefix, len,
						dgen_rom_path.val);
		if (prompt.complete == NULL)
			return NULL;
		rehash_prompt_complete_common();
	}
retry:
	skip = prompt.skip;
	for (i = 0; (prompt.complete[i] != NULL); ++i) {
		if (skip == 0)
			break;
		--skip;
	}
	if (prompt.complete[i] == NULL) {
		if (prompt.skip != 0) {
			prompt.skip = 0;
			goto retry;
		}
		return NULL;
	}
	++prompt.skip;
	return strdup(prompt.complete[i]);
}

static int prompt_cmd_unload(class md& md, unsigned int, const char**)
{
	extern int slot;
	extern void ram_save(class md&);

	info.length = 0; // clear postponed messages
	pd_message("No cartridge.");
	ram_save(md);
	if (dgen_autosave) {
		slot = 0;
		md_save(md);
	}
	if (md.unplug())
		return (CMD_FAIL | CMD_MSG);
	mdscr_splash();
	return (CMD_OK | CMD_MSG);
}

static char* prompt_cmpl_config_file(class md& md, unsigned int ac,
				     const char** av, unsigned int len)
{
	const char *prefix;
	size_t i;
	unsigned int skip;

	(void)md;
	assert(ac != 0);
	if ((ac == 1) || (len == ~0u) || (av[(ac - 1)] == NULL)) {
		prefix = "";
		len = 0;
	}
	else
		prefix = av[(ac - 1)];
	if (prompt.complete == NULL) {
		// Rebuild cache.
		prompt.skip = 0;
		prompt.complete = complete_path(prefix, len, "config");
		if (prompt.complete == NULL)
			return NULL;
		rehash_prompt_complete_common();
	}
retry:
	skip = prompt.skip;
	for (i = 0; (prompt.complete[i] != NULL); ++i) {
		if (skip == 0)
			break;
		--skip;
	}
	if (prompt.complete[i] == NULL) {
		if (prompt.skip != 0) {
			prompt.skip = 0;
			goto retry;
		}
		return NULL;
	}
	++prompt.skip;
	return strdup(prompt.complete[i]);
}

static int prompt_rehash_rc_field(const struct rc_field*, md&);

/**
 * Prompt "config_load" command handler.
 * @param md Context.
 * @param ac Number of arguments in av.
 * @param av Arguments.
 * @return Status code.
 */
static int prompt_cmd_config_load(class md& md, unsigned int ac,
				  const char** av)
{
	FILE *f;
	char *s;
	unsigned int i;

	if (ac != 2)
		return CMD_EINVAL;
	s = backslashify((const uint8_t *)av[1], strlen(av[1]), 0, NULL);
	if (s == NULL)
		return CMD_FAIL;
	f = dgen_fopen("config", av[1], (DGEN_READ | DGEN_CURRENT));
	if (f == NULL) {
		pd_message("Cannot load configuration \"%s\": %s.",
			   s, strerror(errno));
		free(s);
		return (CMD_FAIL | CMD_MSG);
	}
	parse_rc(f, av[1]);
	fclose(f);
	for (i = 0; (rc_fields[i].fieldname != NULL); ++i)
		prompt_rehash_rc_field(&rc_fields[i], md);
	pd_message("Loaded configuration \"%s\".", s);
	free(s);
	return (CMD_OK | CMD_MSG);
}

/**
 * Prompt "config_save" command handler.
 * @param md Context.
 * @param ac Number of arguments in av.
 * @param av Arguments.
 * @return Status code.
 */
static int prompt_cmd_config_save(class md& md, unsigned int ac,
				  const char** av)
{
	FILE *f;
	char *s;

	(void)md;
	if (ac != 2)
		return CMD_EINVAL;
	s = backslashify((const uint8_t *)av[1], strlen(av[1]), 0, NULL);
	if (s == NULL)
		return CMD_FAIL;
	f = dgen_fopen("config", av[1], (DGEN_WRITE | DGEN_TEXT));
	if (f == NULL) {
		pd_message("Cannot save configuration \"%s\": %s",
			   s, strerror(errno));
		free(s);
		return (CMD_FAIL | CMD_MSG);
	}
	dump_rc(f);
	fclose(f);
	pd_message("Saved configuration \"%s\"", s);
	free(s);
	return (CMD_OK | CMD_MSG);
}

static int prompt_cmd_reset(class md& md, unsigned int, const char**)
{
	md.reset();
	return CMD_OK;
}

static int prompt_cmd_unbind(class md&, unsigned int ac, const char** av)
{
	unsigned int i;
	int ret;

	if (ac < 2)
		return CMD_EINVAL;
	ret = CMD_FAIL;
	for (i = 1; (i != ac); ++i) {
		struct rc_field *rcf = rc_fields;

		while (rcf->fieldname != NULL) {
			if ((rcf->parser == rc_bind) &&
			    (!strcasecmp(av[i], rcf->fieldname))) {
				rc_binding_del(rcf);
				ret = CMD_OK;
			}
			else
				++rcf;
		}
	}
	return ret;
}

static char* prompt_cmpl_unbind(class md&, unsigned int ac,
				const char** av, unsigned int len)
{
	const struct rc_binding *rcb;
	const char *prefix;
	unsigned int skip;

	assert(ac != 0);
	if ((ac == 1) || (len == ~0u) || (av[(ac - 1)] == NULL)) {
		prefix = "";
		len = 0;
	}
	else
		prefix = av[(ac - 1)];
	skip = prompt.skip;
retry:
	for (rcb = rc_binding_head.next;
	     (rcb != &rc_binding_head);
	     rcb = rcb->next) {
		if (strncasecmp(prefix, rcb->rc, len))
			continue;
		if (skip == 0)
			break;
		--skip;
	}
	if (rcb == &rc_binding_head) {
		if (prompt.skip != 0) {
			prompt.skip = 0;
			goto retry;
		}
		return NULL;
	}
	++prompt.skip;
	return strdup(rcb->rc);
}

#ifdef WITH_VGMDUMP

static char* prompt_cmpl_vgmdump(class md& md, unsigned int ac,
				 const char** av, unsigned int len)
{
	const char *prefix;
	size_t i;
	unsigned int skip;

	(void)md;
	assert(ac != 0);
	if ((ac == 1) || (len == ~0u) || (av[(ac - 1)] == NULL)) {
		prefix = "";
		len = 0;
	}
	else
		prefix = av[(ac - 1)];
	if (prompt.complete == NULL) {
		// Rebuild cache.
		prompt.skip = 0;
		prompt.complete = complete_path(prefix, len, "vgm");
		if (prompt.complete == NULL)
			return NULL;
		rehash_prompt_complete_common();
	}
retry:
	skip = prompt.skip;
	for (i = 0; (prompt.complete[i] != NULL); ++i) {
		if (skip == 0)
			break;
		--skip;
	}
	if (prompt.complete[i] == NULL) {
		if (prompt.skip != 0) {
			prompt.skip = 0;
			goto retry;
		}
		return NULL;
	}
	++prompt.skip;
	return strdup(prompt.complete[i]);
}

static int prompt_cmd_vgmdump(class md& md, unsigned int ac, const char** av)
{
	char *s;

	if (ac < 2)
		return CMD_EINVAL;
	if (!strcasecmp(av[1], "stop")) {
		if (md.vgm_dump == false)
			pd_message("VGM dumping already stopped.");
		else {
			md.vgm_dump_stop();
			pd_message("Stopped VGM dumping.");
		}
		return (CMD_OK | CMD_MSG);
	}
	if (strcasecmp(av[1], "start"))
		return CMD_EINVAL;
	if (ac < 3) {
		pd_message("VGM file name required.");
		return (CMD_EINVAL | CMD_MSG);
	}
	s = backslashify((const uint8_t *)av[2], strlen(av[2]), 0, NULL);
	if (s == NULL)
		return CMD_FAIL;
	if (md.vgm_dump_start(av[2])) {
		pd_message("Cannot dump VGM to \"%s\": %s",
			   s, strerror(errno));
		free(s);
		return (CMD_FAIL | CMD_MSG);
	}
	pd_message("Started VGM dumping to \"%s\"", s);
	free(s);
	return (CMD_OK | CMD_MSG);
}

#endif

struct filter_data {
	bpp_t buf; ///< Input or output buffer.
	unsigned int width; ///< Buffer width.
	unsigned int height; ///< Buffer height.
	unsigned int pitch; ///< Number of bytes per line in buffer.
	void *data; ///< Filter-specific data.
	bool updated:1; ///< Filter updated data to match its output.
	bool failed:1; ///< Filter failed.
};

typedef void filter_func_t(const struct filter_data *in,
			   struct filter_data *out);

struct filter {
	const char *name; ///< Filter name.
	filter_func_t *func; ///< Filtering function.
	bool safe:1; ///< Output buffer can be the same as input.
	bool ctv:1; ///< Part of the CTV filters set.
	bool resize:1; ///< Filter resizes input.
};

static filter_func_t filter_scale;
static filter_func_t filter_off;
static filter_func_t filter_stretch;
#ifdef WITH_SCALE2X
static filter_func_t filter_scale2x;
#endif
#ifdef WITH_HQX
static filter_func_t filter_hqx;
#endif
#ifdef WITH_CTV
static filter_func_t filter_blur;
static filter_func_t filter_scanline;
static filter_func_t filter_interlace;
static filter_func_t filter_swab;

static void set_swab();
#endif

static const struct filter filters_available[] = {
	{ "stretch", filter_stretch, false, false, true },
	{ "scale", filter_scale, false, false, true },
#ifdef WITH_SCALE2X
	{ "scale2x", filter_scale2x, false, false, true },
#endif
#ifdef WITH_HQX
	{ "hqx", filter_hqx, false, false, true },
#endif
	{ "none", filter_off, true, false, true },
#ifdef WITH_CTV
	// These filters must match ctv_names in rc.cpp.
	{ "off", filter_off, true, true, false },
	{ "blur", filter_blur, true, true, false },
	{ "scanline", filter_scanline, true, true, false },
	{ "interlace", filter_interlace, true, true, false },
	{ "swab", filter_swab, true, true, false },
#endif
};

static unsigned int filters_stack_size;
static bool filters_stack_default;
static const struct filter *filters_stack[64];
static bpp_t filters_stack_data_buf[2];
static struct filter_data filters_stack_data[1 + elemof(filters_stack)];

/**
 * Return filter structure associated with name.
 * @param name Name of filter.
 * @return Pointer to filter or NULL if not found.
 */
static const struct filter *filters_find(const char *name)
{
	size_t i;

	for (i = 0; (i != elemof(filters_available)); ++i)
		if (!strcasecmp(name, filters_available[i].name))
			return &filters_available[i];
	return NULL;
}

/**
 * Update filters data, reallocate extra buffers if necessary.
 */
static void filters_stack_update()
{
	size_t i;
	const struct filter *f;
	struct filter_data *fd;
	unsigned int buffers;
	bpp_t buf[2];
	struct filter_data in_fd = {
		// Use the same formula as draw_scanline() in ras.cpp to avoid
		// the messy border for any supported depth.
		{ ((uint8_t *)mdscr.data + (mdscr.pitch * 8) + 16) },
		video.width,
		video.height,
		(unsigned int)mdscr.pitch,
		NULL,
		false,
		false,
	};
	struct filter_data out_fd = {
		{ screen.buf.u8 },
		screen.width,
		(screen.height - screen.info_height),
		screen.pitch,
		NULL,
		false,
		false,
	};
	struct filter_data *prev_fd;

	DEBUG(("updating filters data"));
retry:
	assert(filters_stack_size <= elemof(filters_stack));
	buffers = 0;
	buf[0].u8 = filters_stack_data_buf[0].u8;
	buf[1].u8 = filters_stack_data_buf[1].u8;
	// Get the number of defined filters and count how many of them cannot
	// use the same buffer for both input and output.
	// Unless they are on top, "unsafe" filters require extra buffers.
	assert(filters_stack_data[0].data == NULL);
	for (i = 0; (i != elemof(filters_stack)); ++i) {
		if (i == filters_stack_size)
			break;
		f = filters_stack[i];
		assert(f != NULL);
		if ((f->safe == false) && (i != (filters_stack_size - 1)))
			++buffers;
		// Clear filters stack output data.
		free(filters_stack_data[i + 1].data);
	}
	memset(filters_stack_data, 0, sizeof(filters_stack_data));
	// Add a valid default filter if stack is empty.
	if (i == 0) {
		assert(filters_stack_size == 0);
		filters_stack[0] = &filters_available[0];
		++filters_stack_size;
		filters_stack_default = true;
		goto retry;
	}
	// Remove default filter if there is one and stack is not empty.
	else if ((i > 1) && (filters_stack_default == true)) {
		assert(filters_stack_size > 1);
		--filters_stack_size;
		memmove(&filters_stack[0], &filters_stack[1],
			(sizeof(filters_stack[0]) * filters_stack_size));
		filters_stack_default = false;
		goto retry;
	}
	// Check if extra buffers are required.
	if (buffers) {
		if (buffers > 2)
			buffers = 2;
		else {
			// Remove unnecessary buffer.
			free(buf[1].u8);
			buf[1].u8 = NULL;
			filters_stack_data_buf[1].u8 = NULL;
		}
		DEBUG(("requiring %u extra buffer(s)", buffers));
		// Reallocate them.
		for (i = 0; (i != buffers); ++i) {
			size_t size = (screen.pitch * screen.height);

			DEBUG(("temporary buffer %u size: %zu", i, size));
			buf[i].u8 =
				(uint8_t *)realloc((void *)buf[i].u8, size);
			if (size == 0) {
				assert(buf[i].u8 == NULL);
				DEBUG(("freed zero-sized buffer"));
				filters_stack_data_buf[i].u8 = NULL;
				continue;
			}
			if (buf[i].u8 == NULL) {
				// Not good, remove one of the filters that
				// require an extra buffer and try again.
				free(filters_stack_data_buf[i].u8);
				filters_stack_data_buf[i].u8 = NULL;
				for (i = 0;
				     (i < filters_stack_size);
				     ++i) {
					if (filters_stack[i]->safe == true)
						continue;
					--filters_stack_size;
					memmove(&filters_stack[i],
						&filters_stack[i + 1],
						(sizeof(filters_stack[i]) *
						 (filters_stack_size - i)));
					break;
				}
				goto retry;
			}
			filters_stack_data_buf[i].u8 = buf[i].u8;
		}
	}
	else {
		// No extra buffer required, deallocate them.
		DEBUG(("removing temporary buffers"));
		for (i = 0; (i != elemof(buf)); ++i) {
			free(buf[i].u8);
			buf[i].u8 = NULL;
			filters_stack_data_buf[i].u8 = NULL;
		}
	}
	// Update I/O buffers.
	buffers = 0;
	prev_fd = &filters_stack_data[0];
	memcpy(prev_fd, &in_fd, sizeof(*prev_fd));
	for (i = 0; (i != elemof(filters_stack)); ++i) {
		if (i == filters_stack_size)
			break;
		f = filters_stack[i];
		fd = &filters_stack_data[i + 1];
		// The last filter uses screen output.
		if (i == (filters_stack_size - 1))
			memcpy(fd, &out_fd, sizeof(*fd));
		// Safe filters have the same input as their output.
		else if (f->safe == true)
			memcpy(fd, prev_fd, sizeof(*fd));
		// Other filters output to a temporary buffer.
		else {
			fd->buf.u8 = buf[buffers].u8;
			fd->width = screen.width;
			fd->height = (screen.height - screen.info_height);
			fd->pitch = screen.pitch;
			fd->data = NULL;
			fd->updated = false;
			fd->failed = false;
			buffers ^= 1;
		}
		prev_fd = fd;
	}
#ifndef NDEBUG
	DEBUG(("filters stack:"));
	for (i = 0; (i != filters_stack_size); ++i)
		DEBUG(("- %s (input: %p output: %p)",
		       filters_stack[i]->name,
		       (void *)filters_stack_data[i].buf.u8,
		       (void *)filters_stack_data[i + 1].buf.u8));
#endif
	screen_clear();
}

/**
 * Add filter to stack.
 * @param f Filter to add.
 */
static void filters_push(const struct filter *f)
{
	assert(filters_stack_size <= elemof(filters_stack));
	if ((f == NULL) || (filters_stack_size == elemof(filters_stack)))
		return;
	DEBUG(("%s", f->name));
	filters_stack[filters_stack_size] = f;
	filters_stack_data[filters_stack_size + 1].data = NULL;
	++filters_stack_size;
	filters_stack_update();
}

/**
 * Insert filter at the bottom of the stack.
 * @param f Filter to insert.
 */
static void filters_insert(const struct filter *f)
{
	assert(filters_stack_size <= elemof(filters_stack));
	if ((f == NULL) ||
	    (filters_stack_size == elemof(filters_stack)))
		return;
	DEBUG(("%s", f->name));
	memmove(&filters_stack[1], &filters_stack[0],
		(filters_stack_size * sizeof(filters_stack[0])));
	filters_stack[0] = f;
	filters_stack_data[0 + 1].data = NULL;
	++filters_stack_size;
	filters_stack_update();
}

// Currently unused.
#if 0

/**
 * Add filter to stack if not already in it.
 * @param f Filter to add.
 */
static void filters_push_once(const struct filter *f)
{
	size_t i;

	assert(filters_stack_size <= elemof(filters_stack));
	if (f == NULL)
		return;
	DEBUG(("%s", f->name));
	for (i = 0; (i != filters_stack_size); ++i)
		if (filters_stack[i] == f)
			return;
	filters_push(f);
}

#endif

#ifdef WITH_CTV

/**
 * Remove last filter from stack.
 */
static void filters_pop()
{
	assert(filters_stack_size <= elemof(filters_stack));
	if (filters_stack_size) {
		--filters_stack_size;
		DEBUG(("%s", filters_stack[filters_stack_size]->name));
		free(filters_stack_data[filters_stack_size + 1].data);
#ifndef NDEBUG
		memset(&filters_stack[filters_stack_size], 0xf0,
		       sizeof(filters_stack[filters_stack_size]));
		memset(&filters_stack_data[filters_stack_size + 1], 0xf1,
		       sizeof(filters_stack_data[filters_stack_size + 1]));
#endif
	}
	filters_stack_update();
}

#endif

/**
 * Remove a filter from anywhere in the stack.
 * @param index Filters stack index.
 */
static void filters_remove(unsigned int index)
{
	assert(filters_stack_size <= elemof(filters_stack));
	if (index >= filters_stack_size)
		return;
	--filters_stack_size;
	DEBUG(("%s", filters_stack[index]->name));
	free(filters_stack_data[index + 1].data);
#ifndef NDEBUG
	memset(&filters_stack[index], 0xf2, sizeof(filters_stack[index]));
	memset(&filters_stack_data[index + 1], 0xf3,
	       sizeof(filters_stack_data[index + 1]));
#endif
	memmove(&filters_stack[index], &filters_stack[index + 1],
		(sizeof(filters_stack[index]) * (filters_stack_size - index)));
	memmove(&filters_stack_data[index + 1], &filters_stack_data[index + 2],
		(sizeof(filters_stack_data[index + 1]) *
		 (filters_stack_size - index)));
	filters_stack_update();
}

/**
 * Remove all occurences of filter from the stack.
 * @param f Filter to remove.
 */
static void filters_pluck(const struct filter *f)
{
	size_t i;

	assert(filters_stack_size <= elemof(filters_stack));
	if (f == NULL)
		return;
	DEBUG(("%s", f->name));
	for (i = 0; (i < filters_stack_size); ++i) {
		if (filters_stack[i] != f)
			continue;
		--filters_stack_size;
		DEBUG(("%s", filters_stack[i]->name));
		free(filters_stack_data[i + 1].data);
#ifndef NDEBUG
		memset(&filters_stack[i], 0xf4, sizeof(filters_stack[i]));
		memset(&filters_stack_data[i + 1], 0xf5,
		       sizeof(filters_stack_data[i + 1]));
#endif
		memmove(&filters_stack[i], &filters_stack[i + 1],
			(sizeof(filters_stack[i]) * (filters_stack_size - i)));
		memmove(&filters_stack_data[i + 1], &filters_stack_data[i + 2],
			(sizeof(filters_stack_data[i + 1]) *
			 (filters_stack_size - i)));
		--i;
	}
	filters_stack_update();
}

#ifdef WITH_CTV

/**
 * Remove all occurences of CTV filters from the stack.
 */
static void filters_pluck_ctv()
{
	size_t i;

	assert(filters_stack_size <= elemof(filters_stack));
	for (i = 0; (i < filters_stack_size); ++i) {
		if (filters_stack[i]->ctv == false)
			continue;
		--filters_stack_size;
		DEBUG(("%s", filters_stack[i]->name));
		free(filters_stack_data[i + 1].data);
#ifndef NDEBUG
		memset(&filters_stack[i], 0xf6, sizeof(filters_stack[i]));
		memset(&filters_stack_data[i + 1], 0xf6,
		       sizeof(filters_stack_data[i + 1]));
#endif
		memmove(&filters_stack[i], &filters_stack[i + 1],
			(sizeof(filters_stack[i]) * (filters_stack_size - i)));
		memmove(&filters_stack_data[i + 1], &filters_stack_data[i + 2],
			(sizeof(filters_stack_data[i + 1]) *
			 (filters_stack_size - i)));
		--i;
	}
	filters_stack_update();
}

#endif

#ifdef WITH_CTV

/**
 * Empty filters stack.
 */
static void filters_empty()
{
	size_t i;

	assert(filters_stack_size <= elemof(filters_stack));
	DEBUG(("stack size was %u", filters_stack_size));
	for (i = 0; (i < filters_stack_size); ++i)
		free(filters_stack_data[i + 1].data);
	filters_stack_size = 0;
#ifndef NDEBUG
	memset(filters_stack, 0xb0, sizeof(filters_stack));
	memset(filters_stack_data, 0xb0, sizeof(filters_stack_data));
	filters_stack_data[0].data = NULL;
#endif
	filters_stack_update();
}

#endif

/**
 * Take a screenshot.
 */
static void do_screenshot(md& megad)
{
	static unsigned int n = 0;
	static char romname_old[sizeof(megad.romname)];
	FILE *fp;
#ifdef HAVE_FTELLO
	off_t pos;
#else
	long pos;
#endif
	bpp_t line;
	unsigned int width;
	unsigned int height;
	unsigned int pitch;
	unsigned int bpp = mdscr.bpp;
	uint8_t (*out)[3]; // 24 bpp
	char name[(sizeof(megad.romname) + 32)];

	if (dgen_raw_screenshots) {
		width = video.width;
		height = video.height;
		pitch = mdscr.pitch;
		line.u8 = ((uint8_t *)mdscr.data + (pitch * 8) + 16);
	}
	else {
		width = screen.width;
		height = screen.height;
		pitch = screen.pitch;
		line = screen.buf;
	}
	switch (bpp) {
	case 15:
	case 16:
	case 24:
	case 32:
		break;
	default:
		pd_message("Screenshots unsupported in %d bpp.", bpp);
		return;
	}
	// Make take a long time, let the main loop know about it.
	stopped = 1;
	// If megad.romname is different from last time, reset n.
	if (memcmp(romname_old, megad.romname, sizeof(romname_old))) {
		memcpy(romname_old, megad.romname, sizeof(romname_old));
		n = 0;
	}
retry:
	snprintf(name, sizeof(name), "%s-%06u.tga",
		 ((megad.romname[0] == '\0') ? "unknown" : megad.romname), n);
	fp = dgen_fopen("screenshots", name, DGEN_APPEND);
	if (fp == NULL) {
		pd_message("Can't open %s.", name);
		return;
	}
	fseek(fp, 0, SEEK_END);
#ifdef HAVE_FTELLO
	pos = ftello(fp);
#else
	pos = ftell(fp);
#endif
	if (((off_t)pos == (off_t)-1) || ((off_t)pos != (off_t)0)) {
		fclose(fp);
		n = ((n + 1) % 1000000);
		goto retry;
	}
	// Allocate line buffer.
	if ((out = (uint8_t (*)[3])malloc(sizeof(*out) * width)) == NULL)
		goto error;
	// Header
	{
		uint8_t tmp[(3 + 5)] = {
			0x00, // length of the image ID field
			0x00, // whether a color map is included
			0x02 // image type: uncompressed, true-color image
			// 5 bytes of color map specification
		};

		if (!fwrite(tmp, sizeof(tmp), 1, fp))
			goto error;
	}
	{
		uint16_t tmp[4] = {
			0, // x-origin
			0, // y-origin
			h2le16(width), // width
			h2le16(height) // height
		};

		if (!fwrite(tmp, sizeof(tmp), 1, fp))
			goto error;
	}
	{
		uint8_t tmp[2] = {
			24, // always output 24 bits per pixel
			(1 << 5) // top-left origin
		};

		if (!fwrite(tmp, sizeof(tmp), 1, fp))
			goto error;
	}
	// Data
	switch (bpp) {
		unsigned int y;
		unsigned int x;

	case 15:
		for (y = 0; (y < height); ++y) {
			if (screen_lock())
				goto error;
			for (x = 0; (x < width); ++x) {
				uint16_t v = line.u16[x];

				out[x][0] = ((v << 3) & 0xf8);
				out[x][1] = ((v >> 2) & 0xf8);
				out[x][2] = ((v >> 7) & 0xf8);
			}
			screen_unlock();
			if (!fwrite(out, (sizeof(*out) * width), 1, fp))
				goto error;
			line.u8 += pitch;
		}
		break;
	case 16:
		for (y = 0; (y < height); ++y) {
			if (screen_lock())
				goto error;
			for (x = 0; (x < width); ++x) {
				uint16_t v = line.u16[x];

				out[x][0] = ((v << 3) & 0xf8);
				out[x][1] = ((v >> 3) & 0xfc);
				out[x][2] = ((v >> 8) & 0xf8);
			}
			screen_unlock();
			if (!fwrite(out, (sizeof(*out) * width), 1, fp))
				goto error;
			line.u8 += pitch;
		}
		break;
	case 24:
		for (y = 0; (y < height); ++y) {
			if (screen_lock())
				goto error;
#ifdef WORDS_BIGENDIAN
			for (x = 0; (x < width); ++x) {
				out[x][0] = line.u24[x][2];
				out[x][1] = line.u24[x][1];
				out[x][2] = line.u24[x][0];
			}
#else
			memcpy(out, line.u24, (sizeof(*out) * width));
#endif
			screen_unlock();
			if (!fwrite(out, (sizeof(*out) * width), 1, fp))
				goto error;
			line.u8 += pitch;
		}
		break;
	case 32:
		for (y = 0; (y < height); ++y) {
			if (screen_lock())
				goto error;
			for (x = 0; (x < width); ++x) {
#ifdef WORDS_BIGENDIAN
				uint32_t rgb = h2le32(line.u32[x]);

				memcpy(&(out[x]), &rgb, 3);
#else
				memcpy(&(out[x]), &(line.u32[x]), 3);
#endif
			}
			screen_unlock();
			if (!fwrite(out, (sizeof(*out) * width), 1, fp))
				goto error;
			line.u8 += pitch;
		}
		break;
	}
	pd_message("Screenshot written to %s.", name);
	free(out);
	fclose(fp);
	return;
error:
	pd_message("Error while generating screenshot %s.", name);
	free(out);
	fclose(fp);
}

/**
 * SDL flags help.
 */
void pd_help()
{
  printf(
#ifdef WITH_OPENGL
  "    -g (1|0)        Enable/disable OpenGL.\n"
#endif
  "    -f              Attempt to run fullscreen.\n"
  "    -X scale        Scale the screen in the X direction.\n"
  "    -Y scale        Scale the screen in the Y direction.\n"
  "    -S scale        Scale the screen by the same amount in both directions.\n"
  "    -G WxH          Desired window size.\n"
  );
}

/**
 * Handle rc variables
 */
void pd_rc()
{
	// Set stuff up from the rcfile first, so we can override it with
	// command-line options
	if (dgen_scale >= 1) {
		dgen_x_scale = dgen_scale;
		dgen_y_scale = dgen_scale;
	}
#ifdef WITH_CTV
	set_swab();
#endif
}

/**
 * Handle the switches.
 * @param c Switch's value.
 */
void pd_option(char c, const char *)
{
	int xs, ys;

	switch (c) {
#ifdef WITH_OPENGL
	case 'g':
		dgen_opengl = atoi(optarg);
		break;
#endif
	case 'f':
		dgen_fullscreen = 1;
		break;
	case 'X':
		if ((xs = atoi(optarg)) <= 0)
			break;
		dgen_x_scale = xs;
		break;
	case 'Y':
		if ((ys = atoi(optarg)) <= 0)
			break;
		dgen_y_scale = ys;
		break;
	case 'S':
		if ((xs = atoi(optarg)) <= 0)
			break;
		dgen_x_scale = xs;
		dgen_y_scale = xs;
		break;
	case 'G':
		if ((sscanf(optarg, " %d x %d ", &xs, &ys) != 2) ||
		    (xs < 0) || (ys < 0))
			break;
		dgen_width = xs;
		dgen_height = ys;
		break;
	}
}

#ifdef WITH_OPENGL

#ifdef WORDS_BIGENDIAN
#define TEXTURE_16_TYPE GL_UNSIGNED_SHORT_5_6_5
#define TEXTURE_32_TYPE GL_UNSIGNED_INT_8_8_8_8_REV
#else
#define TEXTURE_16_TYPE GL_UNSIGNED_SHORT_5_6_5
#define TEXTURE_32_TYPE GL_UNSIGNED_BYTE
#endif

static void texture_init_id(struct texture& texture)
{
	GLint param;

	if (texture.linear)
		param = GL_LINEAR;
	else
		param = GL_NEAREST;
	glBindTexture(GL_TEXTURE_2D, texture.id);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, param);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, param);
	if (texture.u32 == 0)
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
			     texture.width, texture.height,
			     0, GL_RGB, TEXTURE_16_TYPE,
			     texture.buf.u16);
	else
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
			     texture.width, texture.height,
			     0, GL_BGRA, TEXTURE_32_TYPE,
			     texture.buf.u32);
}

static void texture_init_dlist(struct texture& texture)
{
	glNewList(texture.dlist, GL_COMPILE);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0, texture.vis_width, texture.vis_height, 0, 0, 1);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_TEXTURE_2D);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

	glBindTexture(GL_TEXTURE_2D, texture.id);
	glBegin(GL_QUADS);
	glTexCoord2i(0, 1);
	glVertex2i(0, texture.height); // lower left
	glTexCoord2i(0, 0);
	glVertex2i(0, 0); // upper left
	glTexCoord2i(1, 0);
	glVertex2i(texture.width, 0); // upper right
	glTexCoord2i(1, 1);
	glVertex2i(texture.width, texture.height); // lower right
	glEnd();

	glDisable(GL_TEXTURE_2D);
	glPopMatrix();
	glEndList();
}

/**
 * Round a value up to nearest power of two.
 * @param v Value.
 * @return Rounded value.
 */
static uint32_t roundup2(uint32_t v)
{
	--v;
	v |= (v >> 1);
	v |= (v >> 2);
	v |= (v >> 4);
	v |= (v >> 8);
	v |= (v >> 16);
	++v;
	return v;
}

static void release_texture(struct texture& texture)
{
	if ((texture.dlist != 0) && (glIsList(texture.dlist))) {
		glDeleteTextures(1, &texture.id);
		glDeleteLists(texture.dlist, 1);
		texture.dlist = 0;
	}
	free(texture.buf.u32);
	texture.buf.u32 = NULL;
}

static int init_texture(struct screen *screen)
{
	struct texture& texture = screen->texture;
	unsigned int vis_width;
	unsigned int vis_height;
	unsigned int x;
	unsigned int y;
	unsigned int w;
	unsigned int h;
	void *tmp;
	size_t i;
	GLenum error;

	// When bool_opengl_stretch is enabled, width and height are redefined
	// using X and Y scale factors with additional room for the info bar.
	if (dgen_opengl_stretch) {
		vis_width = (video.width *
			     (screen->x_scale ? screen->x_scale : 1));
		vis_height = (video.height *
			      (screen->y_scale ? screen->y_scale : 1));
		vis_height += screen->info_height;
		if (dgen_aspect) {
			// Keep scaled aspect ratio.
			w = ((screen->height * vis_width) / vis_height);
			h = ((screen->width * vis_height) / vis_width);
			if (w >= screen->width) {
				w = screen->width;
				if (h == 0)
					++h;
			}
			else {
				h = screen->height;
				if (w == 0)
					++w;
			}
		}
		else {
			// Aspect ratio won't be kept.
			w = screen->width;
			h = screen->height;
		}
	}
	else {
		w = vis_width = screen->width;
		h = vis_height = screen->height;
	}
	if (screen->width > w)
		x = ((screen->width - w) / 2);
	else
		x = 0;
	if (screen->height > h)
		y = ((screen->height - h) / 2);
	else
		y = 0;
	DEBUG(("initializing for width=%u height=%u", vis_width, vis_height));
	// Set viewport.
	DEBUG(("glViewport(%u, %u, %u, %u)", x, y, w, h));
	glViewport(x, y, w, h);
	// Disable dithering
	glDisable(GL_DITHER);
	// Disable anti-aliasing
	glDisable(GL_LINE_SMOOTH);
	glDisable(GL_POINT_SMOOTH);
	// Disable depth buffer
	glDisable(GL_DEPTH_TEST);

	glClearColor(0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_FLAT);

	// Initialize and allocate texture.
	texture.u32 = (!!dgen_opengl_32bit);
	texture.linear = (!!dgen_opengl_linear);
	texture.width = roundup2(vis_width);
	texture.height = roundup2(vis_height);
	if (dgen_opengl_square) {
		// Texture must be square.
		if (texture.width < texture.height)
			texture.width = texture.height;
		else
			texture.height = texture.width;
	}
	texture.vis_width = vis_width;
	texture.vis_height = vis_height;
	DEBUG(("texture width=%u height=%u", texture.width, texture.height));
	if ((texture.width == 0) || (texture.height == 0))
		goto fail;
	i = ((texture.width * texture.height) * (2 << texture.u32));
	DEBUG(("texture size=%lu (%u Bpp)",
	       (unsigned long)i, (2 << texture.u32)));
	if ((tmp = realloc(texture.buf.u32, i)) == NULL)
		goto fail;
	memset(tmp, 0, i);
	texture.buf.u32 = (uint32_t *)tmp;
	if ((texture.dlist != 0) && (glIsList(texture.dlist))) {
		glDeleteTextures(1, &texture.id);
		glDeleteLists(texture.dlist, 1);
	}
	DEBUG(("texture buf=%p", (void *)texture.buf.u32));
	if ((texture.dlist = glGenLists(1)) == 0)
		goto fail;
	if ((glGenTextures(1, &texture.id), error = glGetError()) ||
	    (texture_init_id(texture), error = glGetError()) ||
	    (texture_init_dlist(texture), error = glGetError())) {
		// Do something with "error".
		goto fail;
	}
	DEBUG(("texture initialization OK"));
	return 0;
fail:
	release_texture(texture);
	DEBUG(("texture initialization failed"));
	return -1;
}

static void update_texture(struct texture& texture)
{
	glBindTexture(GL_TEXTURE_2D, texture.id);
	if (texture.u32 == 0)
		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
				texture.vis_width, texture.vis_height,
				GL_RGB, TEXTURE_16_TYPE,
				texture.buf.u16);
	else
		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
				texture.vis_width, texture.vis_height,
				GL_BGRA, TEXTURE_32_TYPE,
				texture.buf.u32);
	glCallList(texture.dlist);
	SDL_GL_SwapBuffers();
}

#endif // WITH_OPENGL

/**
 * This filter passes input to output unchanged, only centered or truncated
 * if necessary. Doesn't have any fallback, thus cannot fail.
 * @param in Input buffer data.
 * @param out Output buffer data.
 */
static void filter_off(const struct filter_data *in, struct filter_data *out)
{
	unsigned int line;
	unsigned int height;
	uint8_t *in_buf;
	uint8_t *out_buf;

	// Check if copying is necessary.
	if (in->buf.u8 == out->buf.u8)
		return;
	// Copy line by line and center.
	if (in->height > out->height)
		height = out->height;
	else
		height = in->height;
	if (out->updated == false) {
		if (in->width <= out->width) {
			unsigned int x_off = ((out->width - in->width) / 2);
			unsigned int y_off = ((out->height - height) / 2);

			out->buf.u8 += (x_off * screen.Bpp);
			out->buf.u8 += (out->pitch * y_off);
			out->width = in->width;
		}
		out->height = height;
		out->updated = true;
	}
	in_buf = in->buf.u8;
	out_buf = out->buf.u8;
	for (line = 0; (line != height); ++line) {
		memcpy(out_buf, in_buf, (out->width * screen.Bpp));
		in_buf += in->pitch;
		out_buf += out->pitch;
	}
}

// Copy/rescale functions.

struct filter_scale_data {
	unsigned int x_scale;
	unsigned int y_scale;
	filter_func_t *filter;
};

template <typename uintX_t>
static void filter_scale_X(const struct filter_data *in,
			   struct filter_data *out)
{
	struct filter_scale_data *data =
		(struct filter_scale_data *)out->data;
	uintX_t *dst = (uintX_t *)out->buf.u32;
	unsigned int dst_pitch = out->pitch;
	uintX_t *src = (uintX_t *)in->buf.u32;
	unsigned int src_pitch = in->pitch;
	unsigned int width = in->width;
	unsigned int x_scale = data->x_scale;
	unsigned int y_scale = data->y_scale;
	unsigned int height = in->height;
	unsigned int y;

	for (y = 0; (y != height); ++y) {
		uintX_t *out = dst;
		unsigned int i;
		unsigned int x;

		for (x = 0; (x != width); ++x) {
			uintX_t tmp = src[x];

			for (i = 0; (i != x_scale); ++i)
				*(out++) = tmp;
		}
		out = dst;
		dst = (uintX_t *)((uint8_t *)dst + dst_pitch);
		for (i = 1; (i < y_scale); ++i) {
			memcpy(dst, out, (width * sizeof(*dst) * x_scale));
			out = dst;
			dst = (uintX_t *)((uint8_t *)dst + dst_pitch);
		}
		src = (uintX_t *)((uint8_t *)src + src_pitch);
	}
}

static void filter_scale_3(const struct filter_data *in,
			   struct filter_data *out)
{
	struct filter_scale_data *data =
		(struct filter_scale_data *)out->data;
	uint24_t *dst = out->buf.u24;
	unsigned int dst_pitch = out->pitch;
	uint24_t *src = in->buf.u24;
	unsigned int src_pitch = in->pitch;
	unsigned int width = in->width;
	unsigned int x_scale = data->x_scale;
	unsigned int y_scale = data->y_scale;
	unsigned int height = in->height;
	unsigned int y;

	for (y = 0; (y != height); ++y) {
		uint24_t *out = dst;
		unsigned int i;
		unsigned int x;

		for (x = 0; (x != width); ++x) {
			uint24_t tmp;

			u24cpy(&tmp, &src[x]);
			for (i = 0; (i != x_scale); ++i)
				u24cpy((out++), &tmp);
		}
		out = dst;
		dst = (uint24_t *)((uint8_t *)dst + dst_pitch);
		for (i = 1; (i < y_scale); ++i) {
			memcpy(dst, out, (width * sizeof(*dst) * x_scale));
			out = dst;
			dst = (uint24_t *)((uint8_t *)dst + dst_pitch);
		}
		src = (uint24_t *)((uint8_t *)src + src_pitch);
	}
}

/**
 * This filter attempts to rescale according to screen X/Y factors.
 * @param in Input buffer data.
 * @param out Output buffer data.
 */
static void filter_scale(const struct filter_data *in,
			 struct filter_data *out)
{
	static const struct {
		unsigned int Bpp;
		filter_func_t *func;
	} scale_mode[] = {
		{ 1, filter_scale_X<uint8_t> },
		{ 2, filter_scale_X<uint16_t> },
		{ 3, filter_scale_3 },
		{ 4, filter_scale_X<uint32_t> },
	};
	struct filter_scale_data *data;
	unsigned int width;
	unsigned int height;
	unsigned int x_off;
	unsigned int y_off;
	unsigned int x_scale;
	unsigned int y_scale;
	filter_func_t *filter;
	unsigned int i;

	if (out->failed == true) {
	failed:
		filter_off(in, out);
		return;
	}
	if (out->updated == true) {
		data = (struct filter_scale_data *)out->data;
		filter = data->filter;
	process:
		// Feed this to the basic scaler.
		(*filter)(in, out);
		return;
	}
	// Initialize filter.
	assert(out->data == NULL);
	x_scale = screen.x_scale;
	y_scale = screen.y_scale;
	while ((width = (in->width * x_scale)) > out->width)
		--x_scale;
	while ((height = (in->height * y_scale)) > out->height)
		--y_scale;
	// Check whether output is large enough.
	if ((x_scale == 0) || (y_scale == 0)) {
		DEBUG(("cannot rescale by %ux%u", x_scale, y_scale));
		out->failed = true;
		goto failed;
	}
	// Not rescaling is faster through filter_off().
	if ((x_scale == 1) && (y_scale == 1)) {
		DEBUG(("using faster fallback for %ux%u", x_scale, y_scale));
		out->failed = true;
		goto failed;
	}
	// Find a suitable filter.
	for (i = 0; (i != elemof(scale_mode)); ++i)
		if (scale_mode[i].Bpp == screen.Bpp)
			break;
	if (i == elemof(scale_mode)) {
		DEBUG(("%u Bpp depth is not supported", screen.Bpp));
		out->failed = true;
		goto failed;
	}
	DEBUG(("using %u Bpp function to scale by %ux%u",
	       screen.Bpp, x_scale, y_scale));
	data = (struct filter_scale_data *)malloc(sizeof(*data));
	if (data == NULL) {
		DEBUG(("allocation failure"));
		out->failed = true;
		goto failed;
	}
	filter = scale_mode[i].func;
	data->filter = filter;
	data->x_scale = x_scale;
	data->y_scale = y_scale;
	// Center output.
	x_off = ((out->width - width) / 2);
	y_off = ((out->height - height) / 2);
	out->buf.u8 += (x_off * screen.Bpp);
	out->buf.u8 += (out->pitch * y_off);
	out->width = width;
	out->height = height;
	out->data = (void *)data;
	out->updated = true;
	goto process;
}

struct filter_stretch_data {
	uint8_t *h_table;
	uint8_t *v_table;
	filter_func_t *filter;
};

template <typename uintX_t>
static void filter_stretch_X(const struct filter_data *in,
			     struct filter_data *out)
{
	struct filter_stretch_data *data =
		(struct filter_stretch_data *)out->data;
	uint8_t *h_table = data->h_table;
	uint8_t *v_table = data->v_table;
	uintX_t *dst = (uintX_t *)out->buf.u8;
	unsigned int dst_pitch = out->pitch;
	unsigned int dst_w = out->width;
	uintX_t *src = (uintX_t *)in->buf.u8;
	unsigned int src_pitch = in->pitch;
	unsigned int src_w = in->width;
	unsigned int src_h = in->height;
	unsigned int src_y;

	dst_pitch /= sizeof(*dst);
	src_pitch /= sizeof(*src);
	for (src_y = 0; (src_y != src_h); ++src_y) {
		uint8_t v_repeat = v_table[src_y];
		unsigned int src_x;
		unsigned int dst_x;

		if (!v_repeat) {
			src += src_pitch;
			continue;
		}
		for (src_x = 0, dst_x = 0; (src_x != src_w); ++src_x) {
			uint8_t h_repeat = h_table[src_x];

			if (!h_repeat)
				continue;
			while (h_repeat--)
				dst[dst_x++] = src[src_x];
		}
		dst += dst_pitch;
		while (--v_repeat) {
			memcpy(dst, (dst - dst_pitch), (dst_w * sizeof(*dst)));
			dst += dst_pitch;
		}
		src += src_pitch;
	}
}

static void filter_stretch_3(const struct filter_data *in,
			     struct filter_data *out)
{
	struct filter_stretch_data *data =
		(struct filter_stretch_data *)out->data;
	uint8_t *h_table = data->h_table;
	uint8_t *v_table = data->v_table;
	uint24_t *dst = out->buf.u24;
	unsigned int dst_pitch = out->pitch;
	unsigned int dst_w = out->width;
	uint24_t *src = in->buf.u24;
	unsigned int src_pitch = in->pitch;
	unsigned int src_w = in->width;
	unsigned int src_h = in->height;
	unsigned int src_y;

	dst_pitch /= sizeof(*dst);
	src_pitch /= sizeof(*src);
	for (src_y = 0; (src_y != src_h); ++src_y) {
		uint8_t v_repeat = v_table[src_y];
		unsigned int src_x;
		unsigned int dst_x;

		if (!v_repeat) {
			src += src_pitch;
			continue;
		}
		for (src_x = 0, dst_x = 0; (src_x != src_w); ++src_x) {
			uint8_t h_repeat = h_table[src_x];

			if (!h_repeat)
				continue;
			while (h_repeat--)
				u24cpy(&dst[dst_x++], &src[src_x]);
		}
		dst += dst_pitch;
		while (--v_repeat) {
			memcpy(dst, (dst - dst_pitch), (dst_w * sizeof(*dst)));
			dst += dst_pitch;
		}
		src += src_pitch;
	}
}

/**
 * This filter stretches the input buffer to fill the entire output.
 * @param in Input buffer data.
 * @param out Output buffer data.
 */
static void filter_stretch(const struct filter_data *in,
			   struct filter_data *out)
{
	static const struct {
		unsigned int Bpp;
		filter_func_t *func;
	} stretch_mode[] = {
		{ 1, filter_stretch_X<uint8_t> },
		{ 2, filter_stretch_X<uint16_t> },
		{ 3, filter_stretch_3 },
		{ 4, filter_stretch_X<uint32_t> },
	};
	struct filter_stretch_data *data;
	unsigned int dst_w;
	unsigned int dst_h;
	unsigned int src_w;
	unsigned int src_h;
	unsigned int h_ratio;
	unsigned int v_ratio;
	unsigned int dst_x;
	unsigned int dst_y;
	unsigned int src_x;
	unsigned int src_y;
	filter_func_t *filter;
	unsigned int i;

	if (out->failed == true) {
	failed:
		filter_off(in, out);
		return;
	}
	if (out->updated == true) {
		data = (struct filter_stretch_data *)out->data;
		filter = data->filter;
	process:
		(*filter)(in, out);
		return;
	}
	// Initialize filter.
	assert(out->data == NULL);
	dst_w = out->width;
	dst_h = out->height;
	src_w = in->width;
	src_h = in->height;
	if ((src_h == 0) || (src_w == 0)) {
		DEBUG(("invalid input size: %ux%u", src_h, src_w));
		out->failed = true;
		goto failed;
	}
	// Make sure input and output pitches are multiples of pixel size
	// at the current depth.
	if ((in->pitch % screen.Bpp) || (out->pitch % screen.Bpp)) {
		DEBUG(("Bpp: %u, in->pitch: %u, out->pitch: %u",
		       screen.Bpp, in->pitch, out->pitch));
		out->failed = true;
		goto failed;
	}
	// Find a suitable filter.
	for (i = 0; (i != elemof(stretch_mode)); ++i)
		if (stretch_mode[i].Bpp == screen.Bpp)
			break;
	if (i == elemof(stretch_mode)) {
		DEBUG(("%u Bpp depth is not supported", screen.Bpp));
		out->failed = true;
		goto failed;
	}
	filter = stretch_mode[i].func;
	// Fix output if original aspect ratio must be kept.
	if (dgen_aspect) {
		unsigned int w = ((dst_h * src_w) / src_h);
		unsigned int h = ((dst_w * src_h) / src_w);

		if (w >= dst_w) {
			w = dst_w;
			if (h == 0)
				++h;
		}
		else {
			h = dst_h;
			if (w == 0)
				++w;
		}
		dst_w = w;
		dst_h = h;
	}
	// Precompute H and V pixel ratios.
	h_ratio = ((dst_w << 10) / src_w);
	v_ratio = ((dst_h << 10) / src_h);
	data = (struct filter_stretch_data *)
		calloc(1, sizeof(*data) + src_w + src_h);
	if (data == NULL) {
		DEBUG(("allocation failure"));
		out->failed = true;
		goto failed;
	}
	DEBUG(("stretching %ux%u to %ux%u/%ux%u (aspect ratio %s)",
	       src_w, src_h, dst_w, dst_h, out->width, out->height,
	       (dgen_aspect ? "must be kept" : "is free")));
	data->h_table = (uint8_t *)(data + 1);
	data->v_table = (data->h_table + src_w);
	data->filter = filter;
	for (dst_x = 0; (dst_x != dst_w); ++dst_x) {
		src_x = ((dst_x << 10) / h_ratio);
		if (src_x < src_w)
			++data->h_table[src_x];
	}
	for (dst_y = 0; (dst_y != dst_h); ++dst_y) {
		src_y = ((dst_y << 10) / v_ratio);
		if (src_y < src_h)
			++data->v_table[src_y];
	}
	// Center output.
	dst_x = ((out->width - dst_w) / 2);
	dst_y = ((out->height - dst_h) / 2);
	out->buf.u8 += (dst_x * screen.Bpp);
	out->buf.u8 += (out->pitch * dst_y);
	out->width = dst_w;
	out->height = dst_h;
	out->data = (void *)data;
	out->updated = true;
	goto process;
}

#ifdef WITH_HQX

/**
 * This filter attempts to rescale with HQX.
 * @param in Input buffer data.
 * @param out Output buffer data.
 */
static void filter_hqx(const struct filter_data *in, struct filter_data *out)
{
	typedef void hqx_func_t(void *src, uint32_t src_pitch,
				void *dst, uint32_t dst_pitch,
				int width, int height);

	static const struct {
		unsigned int Bpp;
		unsigned int scale;
		hqx_func_t *func;
	} hqx_mode[] = {
		{ 2, 2, (hqx_func_t *)hq2x_16_rb },
		{ 2, 3, (hqx_func_t *)hq3x_16_rb },
		{ 2, 4, (hqx_func_t *)hq4x_16_rb },
		{ 3, 2, (hqx_func_t *)hq2x_24_rb },
		{ 3, 3, (hqx_func_t *)hq3x_24_rb },
		{ 3, 4, (hqx_func_t *)hq4x_24_rb },
		{ 4, 2, (hqx_func_t *)hq2x_32_rb },
		{ 4, 3, (hqx_func_t *)hq3x_32_rb },
		{ 4, 4, (hqx_func_t *)hq4x_32_rb },
	};
	static bool hqx_initialized = false;
	unsigned int width;
	unsigned int height;
	unsigned int x_off;
	unsigned int y_off;
	unsigned int x_scale;
	unsigned int y_scale;
	hqx_func_t *hqx;
	unsigned int i;

	if (out->failed == true) {
	failed:
		filter_off(in, out);
		return;
	}
	if (out->updated == true) {
		hqx = *(hqx_func_t **)out->data;
	process:
		// Feed this to HQX.
		(*hqx)((void *)in->buf.u32, in->pitch,
		       (void *)out->buf.u32, out->pitch,
		       in->width, in->height);
		return;
	}
	// Initialize filter.
	assert(out->data == NULL);
	x_scale = screen.x_scale;
	y_scale = screen.y_scale;
retry:
	while ((width = (in->width * x_scale)) > out->width)
		--x_scale;
	while ((height = (in->height * y_scale)) > out->height)
		--y_scale;
	// Check whether output is large enough.
	if ((x_scale == 0) || (y_scale == 0)) {
		DEBUG(("cannot rescale by %ux%u", x_scale, y_scale));
		out->failed = true;
		goto failed;
	}
	// Find a suitable combination.
	for (i = 0; (i != elemof(hqx_mode)); ++i)
		if ((hqx_mode[i].Bpp == screen.Bpp) &&
		    (hqx_mode[i].scale == x_scale) &&
		    (hqx_mode[i].scale == y_scale))
			break;
	if (i == elemof(hqx_mode)) {
		// Nothing matches, find something that fits.
		DEBUG(("%ux%u @%u Bpp scale factor not supported, trying"
		       " another",
		       x_scale, y_scale, screen.Bpp));
		// Must be square.
		if (x_scale > y_scale)
			x_scale = y_scale;
		else if (y_scale > x_scale)
			y_scale = x_scale;
		assert(x_scale == y_scale);
		(void)y_scale;
		do {
			--i;
			if ((hqx_mode[i].Bpp == screen.Bpp) &&
			    (hqx_mode[i].scale <= x_scale)) {
				x_scale = hqx_mode[i].scale;
				y_scale = hqx_mode[i].scale;
				goto retry;
			}
		}
		while (i != 0);
		DEBUG(("failed to use %ux%u @%u Bpp scale factor",
		       x_scale, y_scale, screen.Bpp));
		out->failed = true;
		goto failed;
	}
	DEBUG(("using %ux%u @%u Bpp scale factor",
	       x_scale, y_scale, screen.Bpp));
	hqx = hqx_mode[i].func;
	out->data = malloc(sizeof(hqx));
	if (out->data == NULL) {
		DEBUG(("allocation failure"));
		out->failed = true;
		goto failed;
	}
	*(hqx_func_t **)out->data = hqx;
	// Center output.
	x_off = ((out->width - width) / 2);
	y_off = ((out->height - height) / 2);
	out->buf.u8 += (x_off * screen.Bpp);
	out->buf.u8 += (out->pitch * y_off);
	out->width = width;
	out->height = height;
	out->updated = true;
	// Initialize HQX if necessary.
	if (hqx_initialized == false) {
		pd_message_cursor(~0u, "Initializing hqx...");
		stopped = 1;
		hqxInit();
		pd_message_cursor(~0u, "");
		hqx_initialized = true;
	}
	goto process;
}

#endif // WITH_HQX

#ifdef WITH_SCALE2X

/**
 * This filter attempts to rescale with Scale2x.
 * @param in Input buffer data.
 * @param out Output buffer data.
 */
static void filter_scale2x(const struct filter_data *in,
			   struct filter_data *out)
{
	static const struct {
		unsigned int x_scale;
		unsigned int y_scale;
		unsigned int mode;
	} scale2x_mode[] = {
		{ 2, 2, 2 },
		{ 2, 3, 203 },
		{ 2, 4, 204 },
		{ 3, 3, 3 },
		{ 4, 4, 4 }
	};
	unsigned int width;
	unsigned int height;
	unsigned int x_off;
	unsigned int y_off;
	unsigned int x_scale;
	unsigned int y_scale;
	unsigned int mode;
	unsigned int i;

	if (out->failed == true) {
	failed:
		filter_off(in, out);
		return;
	}
	if (out->updated == true) {
		mode = *(unsigned int *)out->data;
	process:
		// Feed this to scale2x.
		scale(mode, out->buf.u32, out->pitch, in->buf.u32, in->pitch,
		      screen.Bpp, in->width, in->height);
		return;
	}
	// Initialize filter.
	assert(out->data == NULL);
	x_scale = screen.x_scale;
	y_scale = screen.y_scale;
retry:
	while ((width = (in->width * x_scale)) > out->width)
		--x_scale;
	while ((height = (in->height * y_scale)) > out->height)
		--y_scale;
	// Check whether output is large enough.
	if ((x_scale == 0) || (y_scale == 0)) {
		DEBUG(("cannot rescale by %ux%u", x_scale, y_scale));
		out->failed = true;
		goto failed;
	}
	// Check whether depth is supported by filter.
	if ((screen.Bpp != 4) && (screen.Bpp != 2)) {
		DEBUG(("unsupported depth %u", screen.bpp));
		out->failed = true;
		goto failed;
	}
	// Find a suitable combination.
	for (i = 0; (i != elemof(scale2x_mode)); ++i)
		if ((scale2x_mode[i].x_scale == x_scale) &&
		    (scale2x_mode[i].y_scale == y_scale))
			break;
	if (i == elemof(scale2x_mode)) {
		// Nothing matches, find something that fits.
		DEBUG(("%ux%u scale factor not supported, trying another",
		       x_scale, y_scale));
		do {
			--i;
			if ((scale2x_mode[i].x_scale <= x_scale) &&
			    (scale2x_mode[i].y_scale <= y_scale)) {
				x_scale = scale2x_mode[i].x_scale;
				y_scale = scale2x_mode[i].y_scale;
				goto retry;
			}
		}
		while (i != 0);
		DEBUG(("failed to use %ux%u scale factor", x_scale, y_scale));
		out->failed = true;
		goto failed;
	}
	DEBUG(("using %ux%u scale factor", x_scale, y_scale));
	mode = scale2x_mode[i].mode;
	out->data = malloc(sizeof(mode));
	if (out->data == NULL) {
		DEBUG(("allocation failure"));
		out->failed = true;
		goto failed;
	}
	*(unsigned int *)out->data = mode;
	// Center output.
	x_off = ((out->width - width) / 2);
	y_off = ((out->height - height) / 2);
	out->buf.u8 += (x_off * screen.Bpp);
	out->buf.u8 += (out->pitch * y_off);
	out->width = width;
	out->height = height;
	out->updated = true;
	goto process;
}

#endif // WITH_SCALE2X

#ifdef WITH_CTV

// "Blur" CTV filters.

static void filter_blur_32(const struct filter_data *in,
			   struct filter_data *out)
{
	bpp_t in_buf = in->buf;
	bpp_t out_buf = out->buf;
	unsigned int xsize = out->width;
	unsigned int ysize = out->height;
	unsigned int y;

	for (y = 0; (y < ysize); ++y) {
		uint32_t old = *in_buf.u32;
		unsigned int x;

		for (x = 0; (x < xsize); ++x) {
			uint32_t tmp = in_buf.u32[x];

			tmp = (((((tmp & 0x00ff00ff) +
				  (old & 0x00ff00ff)) >> 1) & 0x00ff00ff) |
			       ((((tmp & 0xff00ff00) +
				  (old & 0xff00ff00)) >> 1) & 0xff00ff00));
			old = in_buf.u32[x];
			out_buf.u32[x] = tmp;
		}
		in_buf.u8 += in->pitch;
		out_buf.u8 += out->pitch;
	}
}

static void filter_blur_24(const struct filter_data *in,
			   struct filter_data *out)
{
	bpp_t in_buf = in->buf;
	bpp_t out_buf = out->buf;
	unsigned int xsize = out->width;
	unsigned int ysize = out->height;
	unsigned int y;

	for (y = 0; (y < ysize); ++y) {
		uint24_t old;
		unsigned int x;

		u24cpy(&old, in_buf.u24);
		for (x = 0; (x < xsize); ++x) {
			uint24_t tmp;

			u24cpy(&tmp, &in_buf.u24[x]);
			out_buf.u24[x][0] = ((tmp[0] + old[0]) >> 1);
			out_buf.u24[x][1] = ((tmp[1] + old[1]) >> 1);
			out_buf.u24[x][2] = ((tmp[2] + old[2]) >> 1);
			u24cpy(&old, &tmp);
		}
		in_buf.u8 += in->pitch;
		out_buf.u8 += out->pitch;
	}
}

static void filter_blur_16(const struct filter_data *in,
			   struct filter_data *out)
{
	bpp_t in_buf = in->buf;
	bpp_t out_buf = out->buf;
	unsigned int xsize = out->width;
	unsigned int ysize = out->height;
	unsigned int y;

#ifdef WITH_X86_CTV
	if (in_buf.u16 == out_buf.u16) {
		for (y = 0; (y < ysize); ++y) {
			// Blur, by Dave
			blur_bitmap_16((uint8_t *)out_buf.u16, (xsize - 1));
			out_buf.u8 += out->pitch;
		}
		return;
	}
#endif
	for (y = 0; (y < ysize); ++y) {
		uint16_t old = *in_buf.u16;
		unsigned int x;

		for (x = 0; (x < xsize); ++x) {
			uint16_t tmp = in_buf.u16[x];

			tmp = (((((tmp & 0xf81f) +
				  (old & 0xf81f)) >> 1) & 0xf81f) |
			       ((((tmp & 0x07e0) +
				  (old & 0x07e0)) >> 1) & 0x07e0));
			old = in_buf.u16[x];
			out_buf.u16[x] = tmp;
		}
		in_buf.u8 += in->pitch;
		out_buf.u8 += out->pitch;
	}
}

static void filter_blur_15(const struct filter_data *in,
			   struct filter_data *out)
{
	bpp_t in_buf = in->buf;
	bpp_t out_buf = out->buf;
	unsigned int xsize = out->width;
	unsigned int ysize = out->height;
	unsigned int y;

#ifdef WITH_X86_CTV
	if (in_buf.u15 == out_buf.u15) {
		for (y = 0; (y < ysize); ++y) {
			// Blur, by Dave
			blur_bitmap_15((uint8_t *)out_buf.u15, (xsize - 1));
			out_buf.u8 += out->pitch;
		}
		return;
	}
#endif
	for (y = 0; (y < ysize); ++y) {
		uint16_t old = *in_buf.u15;
		unsigned int x;

		for (x = 0; (x < xsize); ++x) {
			uint16_t tmp = in_buf.u15[x];

			tmp = (((((tmp & 0x7c1f) +
				  (old & 0x7c1f)) >> 1) & 0x7c1f) |
			       ((((tmp & 0x03e0) +
				  (old & 0x03e0)) >> 1) & 0x03e0));
			old = in_buf.u15[x];
			out_buf.u15[x] = tmp;
		}
		in_buf.u8 += in->pitch;
		out_buf.u8 += out->pitch;
	}
}

static void filter_blur(const struct filter_data *in,
			struct filter_data *out)
{
	static const struct {
		unsigned int bpp;
		filter_func_t *filter;
	} blur_mode[] = {
		{ 32, filter_blur_32 },
		{ 24, filter_blur_24 },
		{ 16, filter_blur_16 },
		{ 15, filter_blur_15 },
	};
	filter_func_t *blur;

	if (out->failed == true) {
	failed:
		filter_off(in, out);
		return;
	}
	if (out->updated == false) {
		unsigned int i;

		for (i = 0; (i != elemof(blur_mode)); ++i)
			if (blur_mode[i].bpp == screen.bpp)
				break;
		if (i == elemof(blur_mode)) {
			DEBUG(("%u bpp depth is not supported", screen.bpp));
			out->failed = true;
			goto failed;
		}
		blur = blur_mode[i].filter;
		out->data = malloc(sizeof(filter));
		if (out->data == NULL) {
			DEBUG(("allocation failure"));
			out->failed = true;
			goto failed;
		}
		if (in->width <= out->width) {
			unsigned int x_off = ((out->width - in->width) / 2);
			unsigned int y_off = ((out->height - in->height) / 2);

			out->buf.u8 += (x_off * screen.Bpp);
			out->buf.u8 += (out->pitch * y_off);
			out->width = in->width;
		}
		if (in->height <= out->height)
			out->height = in->height;
		*((filter_func_t **)out->data) = blur;
		out->updated = true;
	}
	else
		blur = *(filter_func_t **)out->data;
	(*blur)(in, out);
}

// Scanline/Interlace CTV filters.

static void filter_scanline_frame(const struct filter_data *in,
				  struct filter_data *out)
{
	unsigned int frame = ((unsigned int *)out->data)[0];
	unsigned int bpp = ((unsigned int *)out->data)[1];
	bpp_t in_buf = in->buf;
	bpp_t out_buf = out->buf;
	unsigned int xsize = out->width;
	unsigned int ysize = out->height;

	out_buf.u8 += (out->pitch * !!frame);
	switch (bpp) {
		unsigned int x;
		unsigned int y;

	case 32:
		for (y = frame; (y < ysize); y += 2) {
			for (x = 0; (x < xsize); ++x)
				out_buf.u32[x] =
					((in_buf.u32[x] >> 1) & 0x7f7f7f7f);
			in_buf.u8 += (in->pitch * 2);
			out_buf.u8 += (out->pitch * 2);
		}
		break;
	case 24:
		for (y = frame; (y < ysize); y += 2) {
			for (x = 0; (x < xsize); ++x) {
				out_buf.u24[x][0] = (in_buf.u24[x][0] >> 1);
				out_buf.u24[x][1] = (in_buf.u24[x][1] >> 1);
				out_buf.u24[x][2] = (in_buf.u24[x][2] >> 1);
			}
			in_buf.u8 += (in->pitch * 2);
			out_buf.u8 += (out->pitch * 2);
		}
		break;
	case 16:
		for (y = frame; (y < ysize); y += 2) {
#ifdef WITH_X86_CTV
			if (in_buf.u16 == out_buf.u16) {
				// Scanline, by Phil
				test_ctv((uint8_t *)out_buf.u16, xsize);
			}
			else
#endif
			for (x = 0; (x < xsize); ++x)
				out_buf.u16[x] =
					((in_buf.u16[x] >> 1) & 0x7bef);
			in_buf.u8 += (in->pitch * 2);
			out_buf.u8 += (out->pitch * 2);
		}
		break;
	case 15:
		for (y = frame; (y < ysize); y += 2) {
#ifdef WITH_X86_CTV
			if (in_buf.u15 == out_buf.u15) {
				// Scanline, by Phil
				test_ctv((uint8_t *)out_buf.u16, xsize);
			}
			else
#endif
			for (x = 0; (x < xsize); ++x)
				out_buf.u15[x] =
					((in_buf.u15[x] >> 1) & 0x3def);
			in_buf.u8 += (in->pitch * 2);
			out_buf.u8 += (out->pitch * 2);
		}
		break;
	}
}

static void filter_scanline(const struct filter_data *in,
			    struct filter_data *out)
{
	if (out->failed == true) {
	failed:
		filter_off(in, out);
		return;
	}
	if (out->updated == false) {
		if ((screen.bpp != 32) &&
		    (screen.bpp != 24) &&
		    (screen.bpp != 16) &&
		    (screen.bpp != 15)) {
			DEBUG(("%u bpp depth is not supported", screen.bpp));
			out->failed = true;
			goto failed;
		}
		out->data = malloc(sizeof(unsigned int [2]));
		if (out->data == NULL) {
			DEBUG(("allocation failure"));
			out->failed = true;
			goto failed;
		}
		if (in->width <= out->width) {
			unsigned int x_off = ((out->width - in->width) / 2);
			unsigned int y_off = ((out->height - in->height) / 2);

			out->buf.u8 += (x_off * screen.Bpp);
			out->buf.u8 += (out->pitch * y_off);
			out->width = in->width;
		}
		if (in->height <= out->height)
			out->height = in->height;
		((unsigned int *)out->data)[0] = 0;
		((unsigned int *)out->data)[1] = screen.bpp;
		out->updated = true;
	}
	filter_scanline_frame(in, out);
}

static void filter_interlace(const struct filter_data *in,
			     struct filter_data *out)
{
	if (out->failed == true) {
	failed:
		filter_off(in, out);
		return;
	}
	if (out->updated == false) {
		if ((screen.bpp != 32) &&
		    (screen.bpp != 24) &&
		    (screen.bpp != 16) &&
		    (screen.bpp != 15)) {
			DEBUG(("%u bpp depth is not supported", screen.bpp));
			out->failed = true;
			goto failed;
		}
		out->data = malloc(sizeof(unsigned int [2]));
		if (out->data == NULL) {
			DEBUG(("allocation failure"));
			out->failed = true;
			goto failed;
		}
		if (in->width <= out->width) {
			unsigned int x_off = ((out->width - in->width) / 2);
			unsigned int y_off = ((out->height - in->height) / 2);

			out->buf.u8 += (x_off * screen.Bpp);
			out->buf.u8 += (out->pitch * y_off);
			out->width = in->width;
		}
		if (in->height <= out->height)
			out->height = in->height;
		((unsigned int *)out->data)[0] = 0;
		((unsigned int *)out->data)[1] = screen.bpp;
		out->updated = true;
	}
	filter_scanline_frame(in, out);
	((unsigned int *)out->data)[0] ^= 1;
}

// Byte swap filter.
static void filter_swab(const struct filter_data *in,
			struct filter_data *out)
{
	bpp_t in_buf;
	bpp_t out_buf;
	unsigned int xsize;
	unsigned int ysize;

	if (out->failed == true) {
	failed:
		filter_off(in, out);
		return;
	}
	if (out->updated == false) {
		if ((screen.Bpp != 4) &&
		    (screen.Bpp != 3) &&
		    (screen.Bpp != 2)) {
			DEBUG(("%u Bpp depth is not supported", screen.Bpp));
			out->failed = true;
			goto failed;
		}
		if (in->width <= out->width) {
			unsigned int x_off = ((out->width - in->width) / 2);
			unsigned int y_off = ((out->height - in->height) / 2);

			out->buf.u8 += (x_off * screen.Bpp);
			out->buf.u8 += (out->pitch * y_off);
			out->width = in->width;
		}
		if (in->height <= out->height)
			out->height = in->height;
		out->updated = true;
	}
	in_buf = in->buf;
	out_buf = out->buf;
	ysize = out->height;
	xsize = out->width;
	switch (screen.Bpp) {
		unsigned int x;
		unsigned int y;

	case 4:
		for (y = 0; (y < ysize); ++y) {
			for (x = 0; (x < xsize); ++x) {
				union {
					uint32_t u32;
					uint8_t u8[4];
				} tmp[2];

				tmp[0].u32 = in_buf.u32[x];
				tmp[1].u8[0] = tmp[0].u8[3];
				tmp[1].u8[1] = tmp[0].u8[2];
				tmp[1].u8[2] = tmp[0].u8[1];
				tmp[1].u8[3] = tmp[0].u8[0];
				out_buf.u32[x] = tmp[1].u32;
			}
			in_buf.u8 += in->pitch;
			out_buf.u8 += out->pitch;
		}
		break;
	case 3:
		for (y = 0; (y < ysize); ++y) {
			for (x = 0; (x < xsize); ++x) {
				uint24_t tmp = {
					in_buf.u24[x][2],
					in_buf.u24[x][1],
					in_buf.u24[x][0]
				};

				u24cpy(&out_buf.u24[x], &tmp);
			}
			in_buf.u8 += in->pitch;
			out_buf.u8 += out->pitch;
		}
		break;
	case 2:
		for (y = 0; (y < ysize); ++y) {
			for (x = 0; (x < xsize); ++x)
				out_buf.u16[x] = ((in_buf.u16[x] << 8) |
						  (in_buf.u16[x] >> 8));
			in_buf.u8 += in->pitch;
			out_buf.u8 += out->pitch;
		}
		break;
	}
}

#endif // WITH_CTV

/**
 * Special characters interpreted by filter_text().
 * FILTER_TEXT_BG_NONE  transparent background.
 * FILTER_TEXT_BG_BLACK black background.
 * FILTER_TEXT_7X6      use 7x6 font.
 * FILTER_TEXT_8X13     use 8x13 font.
 * FILTER_TEXT_16X26    use 16x26 font.
 * FILTER_TEXT_CENTER   center justify.
 * FILTER_TEXT_LEFT     left justify.
 * FILTER_TEXT_RIGHT    right justify.
 */
#define FILTER_TEXT_ESCAPE "\033"
#define FILTER_TEXT_BG_NONE FILTER_TEXT_ESCAPE "\x01\x01"
#define FILTER_TEXT_BG_BLACK FILTER_TEXT_ESCAPE "\x01\x02"
#define FILTER_TEXT_7X6 FILTER_TEXT_ESCAPE "\x02\x01"
#define FILTER_TEXT_8X13 FILTER_TEXT_ESCAPE "\x02\x02"
#define FILTER_TEXT_16X26 FILTER_TEXT_ESCAPE "\x02\x03"
#define FILTER_TEXT_CENTER FILTER_TEXT_ESCAPE "\x03\x01"
#define FILTER_TEXT_LEFT FILTER_TEXT_ESCAPE "\x03\x02"
#define FILTER_TEXT_RIGHT FILTER_TEXT_ESCAPE "\x03\x03"

static char filter_text_str[2048];

/**
 * Append message to filter_text_str[].
 */
static void filter_text_msg(const char *fmt, ...)
{
	size_t off;
	size_t len = sizeof(filter_text_str);
	va_list vl;

	assert(filter_text_str[(len - 1)] == '\0');
	off = strlen(filter_text_str);
	len -= off;
	if (len == 0)
		return;
	va_start(vl, fmt);
	vsnprintf(&filter_text_str[off], len, fmt, vl);
	va_end(vl);
}

/**
 * Text overlay filter.
 * @param in Input buffer data.
 * @param out Output buffer data.
 */
static void filter_text(const struct filter_data *in,
			struct filter_data *out)
{
	bpp_t buf = out->buf;
	unsigned int buf_pitch = out->pitch;
	unsigned int xsize = out->width;
	unsigned int ysize = out->height;
	unsigned int bpp = screen.bpp;
	unsigned int Bpp = ((bpp + 1) / 8);
	const char *str = filter_text_str;
	const char *next = str;
	bool clear = false;
	bool flush = false;
	enum { LEFT, CENTER, RIGHT } justify = LEFT;
	const struct {
		enum font_type type;
		unsigned int width;
		unsigned int height;
	} font_data[] = {
		{ FONT_TYPE_7X5, 7, (5 + 1) }, // +1 for vertical spacing.
		{ FONT_TYPE_8X13, 8, 13 },
		{ FONT_TYPE_16X26, 16, 26 }
	}, *font = &font_data[0], *old_font = font;
	unsigned int line_length = 0;
	unsigned int line_off = 0;
	unsigned int line_width = 0;
	unsigned int line_height = font->height;

	// Input is unused.
	(void)in;
	assert(filter_text_str[(sizeof(filter_text_str) - 1)] == '\0');
	while (1) {
		unsigned int len;
		unsigned int width;

		if ((*next == '\0') || (*next == '\n')) {
		trunc:
			if (flush == false) {
				next = str;
				assert(line_width <= xsize);
				switch (justify) {
				case LEFT:
					line_off = 0;
					break;
				case CENTER:
					line_off = ((xsize - line_width) / 2);
					break;
				case RIGHT:
					line_off = (xsize - line_width);
					break;
				}
				if (clear)
					memset(buf.u8, 0,
					       (buf_pitch * line_height));
				font = old_font;
				flush = true;
			}
			else if (*next == '\0')
				break;
			else {
				if (*next == '\n')
					++next;
				str = next;
				old_font = font;
				line_length = 0;
				line_off = 0;
				line_width = 0;
				buf.u8 += (buf_pitch * line_height);
				ysize -= line_height;
				line_height = font->height;
				// Still enough vertical pixels for this line?
				if (ysize < line_height)
					break;
				flush = false;
			}
		}
		else if (*next == *FILTER_TEXT_ESCAPE) {
			const char *tmp;
			size_t sz;

#define FILTER_TEXT_IS(f)				\
			(tmp = (f), sz = strlen(f),	\
			 !strncmp(tmp, next, sz))

			if (FILTER_TEXT_IS(FILTER_TEXT_BG_NONE))
				clear = false;
			else if (FILTER_TEXT_IS(FILTER_TEXT_BG_BLACK))
				clear = true;
			else if (FILTER_TEXT_IS(FILTER_TEXT_CENTER))
				justify = CENTER;
			else if (FILTER_TEXT_IS(FILTER_TEXT_LEFT))
				justify = LEFT;
			else if (FILTER_TEXT_IS(FILTER_TEXT_RIGHT))
				justify = RIGHT;
			else if (FILTER_TEXT_IS(FILTER_TEXT_7X6))
				font = &font_data[0];
			else if (FILTER_TEXT_IS(FILTER_TEXT_8X13))
				font = &font_data[1];
			else if (FILTER_TEXT_IS(FILTER_TEXT_16X26))
				font = &font_data[2];
			next += sz;
		}
		else if ((line_width + font->width) <= xsize) {
			++line_length;
			line_width += font->width;
			if (line_height < font->height) {
				line_height = font->height;
				// Still enough vertical pixels for this line?
				if (ysize < line_height)
					break;
			}
			++next;
		}
		else // Truncate line.
			goto trunc;
		if (flush == false)
			continue;
		// Compute number of characters and width.
		len = 0;
		width = 0;
		while ((len != line_length) &&
		       (next[len] != '\0') &&
		       (next[len] != '\n') &&
		       (next[len] != *FILTER_TEXT_ESCAPE)) {
			width += font->width;
			++len;
		}
		// Display.
		len = font_text((buf.u8 +
				 // Horizontal offset.
				 (line_off * Bpp) +
				 // Vertical offset.
				 ((line_height - font->height) * buf_pitch)),
				(xsize - line_off),
				line_height, Bpp, buf_pitch, next, len, ~0u,
				font->type);
		line_off += width;
		next += len;
	}
}

static const struct filter filter_text_def = {
	"text", filter_text, true, false, false
};

#ifdef WITH_CTV

static void set_swab()
{
	const struct filter *f = filters_find("swab");

	if (f == NULL)
		return;
	filters_pluck(f);
	if (dgen_swab)
		filters_insert(f);
}

static int prompt_cmd_filter_push(class md&, unsigned int ac, const char** av)
{
	unsigned int i;

	if (ac < 2)
		return CMD_EINVAL;
	for (i = 1; (i != ac); ++i) {
		const struct filter *f = filters_find(av[i]);

		if (f == NULL)
			return CMD_EINVAL;
		filters_push(f);
	}
	return CMD_OK;
}

static char* prompt_cmpl_filter_push(class md&, unsigned int ac,
				     const char** av, unsigned int len)
{
	const struct filter *f;
	const char *prefix;
	unsigned int skip;
	unsigned int i;

	assert(ac != 0);
	if ((ac == 1) || (len == ~0u) || (av[(ac - 1)] == NULL)) {
		prefix = "";
		len = 0;
	}
	else
		prefix = av[(ac - 1)];
	skip = prompt.skip;
retry:
	for (i = 0; (i != elemof(filters_available)); ++i) {
		f = &filters_available[i];
		if (strncasecmp(prefix, f->name, len))
			continue;
		if (skip == 0)
			break;
		--skip;
	}
	if (i == elemof(filters_available)) {
		if (prompt.skip != 0) {
			prompt.skip = 0;
			goto retry;
		}
		return NULL;
	}
	++prompt.skip;
	return strdup(f->name);
}

static int prompt_cmd_filter_pop(class md&, unsigned int ac, const char**)
{
	if (ac != 1)
		return CMD_EINVAL;
	filters_pop();
	return CMD_OK;
}

static int prompt_cmd_filter_none(class md&, unsigned int ac, const char**)
{
	if (ac != 1)
		return CMD_EINVAL;
	filters_empty();
	return CMD_OK;
}

#endif // WITH_CTV

static bool calibrating = false; //< True during calibration.
static unsigned int calibrating_controller; ///< Controller being calibrated.

static void manage_calibration(enum rc_binding_type type, intptr_t code);

/**
 * Interactively calibrate a controller.
 * If n_args == 1, controller 0 will be configured.
 * If n_args == 2, configure controller in string args[1].
 * @param n_args Number of arguments.
 * @param[in] args List of arguments.
 * @return Status code.
 */
static int
prompt_cmd_calibrate(class md&, unsigned int n_args, const char** args)
{
	/* check args first */
	if (n_args == 1)
		calibrating_controller = 0;
	else if (n_args == 2) {
		calibrating_controller = (atoi(args[1]) - 1);
		if (calibrating_controller > 1)
			return CMD_EINVAL;
	}
	else
		return CMD_EINVAL;
	manage_calibration(RCB_NUM, -1);
	return (CMD_OK | CMD_MSG);
}

static int set_scaling(const char *name)
{
	unsigned int i = filters_stack_size;

	assert(i <= elemof(filters_stack));
	// Replace all current scalers with these.
	while (i != 0) {
		--i;
		if (filters_stack[i]->resize == true)
			filters_remove(i);
	}
	while (name += strspn(name, " \t\n"), name[0] != '\0') {
		const struct filter *f;
		int len = strcspn(name, " \t\n");
		char token[64];

		snprintf(token, sizeof(token), "%.*s", len, name);
		name += len;
		if (((f = filters_find(token)) == NULL) ||
		    (filters_stack_size == elemof(filters_stack)))
			return -1;
		filters_push(f);
	}
	return 0;
}

/**
 * Display splash screen.
 */
static void mdscr_splash()
{
	unsigned int x;
	unsigned int y;
	bpp_t src;
	unsigned int src_pitch = (dgen_splash_data.width *
				  dgen_splash_data.bytes_per_pixel);
	unsigned int sw = dgen_splash_data.width;
	unsigned int sh = dgen_splash_data.height;
	bpp_t dst;
	unsigned int dst_pitch = mdscr.pitch;
	unsigned int dw = video.width;
	unsigned int dh = video.height;

	if ((dgen_splash_data.bytes_per_pixel != 3) || (sw != dw))
		return;
	src.u8 = (uint8_t *)dgen_splash_data.pixel_data;
	dst.u8 = ((uint8_t *)mdscr.data + (dst_pitch * 8) + 16);
	// Center it.
	if (sh < dh) {
		unsigned int off = ((dh - sh) / 2);

		memset(dst.u8, 0x00, (dst_pitch * off));
		memset(&dst.u8[(dst_pitch * (off + sh))], 0x00,
		       (dst_pitch * (dh - (off + sh))));
		dst.u8 += (dst_pitch * off);
	}
	switch (mdscr.bpp) {
	case 32:
		for (y = 0; ((y != dh) && (y != sh)); ++y) {
			for (x = 0; ((x != dw) && (x != sw)); ++x) {
				dst.u32[x] = ((src.u24[x][0] << 16) |
					      (src.u24[x][1] << 8) |
					      (src.u24[x][2] << 0));
			}
			src.u8 += src_pitch;
			dst.u8 += dst_pitch;
		}
		break;
	case 24:
		for (y = 0; ((y != dh) && (y != sh)); ++y) {
			for (x = 0; ((x != dw) && (x != sw)); ++x) {
				dst.u24[x][0] = src.u24[x][2];
				dst.u24[x][1] = src.u24[x][1];
				dst.u24[x][2] = src.u24[x][0];
			}
			src.u8 += src_pitch;
			dst.u8 += dst_pitch;
		}
		break;
	case 16:
		for (y = 0; ((y != dh) && (y != sh)); ++y) {
			for (x = 0; ((x != dw) && (x != sw)); ++x) {
				dst.u16[x] = (((src.u24[x][0] & 0xf8) << 8) |
					      ((src.u24[x][1] & 0xfc) << 3) |
					      ((src.u24[x][2] & 0xf8) >> 3));
			}
			src.u8 += src_pitch;
			dst.u8 += dst_pitch;
		}
		break;
	case 15:
		for (y = 0; ((y != dh) && (y != sh)); ++y) {
			for (x = 0; ((x != dw) && (x != sw)); ++x) {
				dst.u16[x] = (((src.u24[x][0] & 0xf8) << 7) |
					      ((src.u24[x][1] & 0xf8) << 2) |
					      ((src.u24[x][2] & 0xf8) >> 3));
			}
			src.u8 += src_pitch;
			dst.u8 += dst_pitch;
		}
		break;
	case 8:
		break;
	}
}

/**
 * Initialize screen.
 *
 * @param width Width of display.
 * @param height Height of display.
 * @return 0 on success, -1 if screen could not be initialized with current
 * options but remains in its previous state, -2 if screen is unusable.
 */
static int screen_init(unsigned int width, unsigned int height)
{
	static bool once = true;
	uint32_t flags = (SDL_RESIZABLE | SDL_ANYFORMAT | SDL_HWPALETTE |
			  SDL_HWSURFACE);
	struct screen scrtmp;
	const struct dgen_font *font;

#ifdef WITH_THREADS
	screen_update_thread_stop();
#endif
	DEBUG(("want width=%u height=%u", width, height));
	stopped = 1;
	// Copy current screen data.
	memcpy(&scrtmp, &screen, sizeof(scrtmp));
	if (once) {
		unsigned int info_height = dgen_font[FONT_TYPE_8X13].h;

		// Force defaults once.
		scrtmp.window_width = 0;
		scrtmp.window_height = 0;
		scrtmp.width = (video.width * 2);
		scrtmp.height = ((video.height * 2) + info_height);
		scrtmp.x_scale = (scrtmp.width / video.width);
		scrtmp.y_scale = (scrtmp.height / video.height);
		scrtmp.bpp = 0;
		scrtmp.Bpp = 0;
		scrtmp.info_height = info_height;
		scrtmp.buf.u8 = 0;
		scrtmp.pitch = 0;
		scrtmp.surface = 0;
		scrtmp.want_fullscreen = 0;
		scrtmp.is_fullscreen = 0;
#ifdef WITH_OPENGL
		scrtmp.want_opengl = 0;
		scrtmp.is_opengl = 0;
#endif
#ifdef WITH_THREADS
		scrtmp.want_thread = 0;
		scrtmp.is_thread = 0;
		scrtmp.thread = 0;
		scrtmp.lock = 0;
		scrtmp.cond = 0;
#endif
		memset(scrtmp.color, 0, sizeof(scrtmp.color));
		once = false;
	}
	// Use configuration data.
	if (width != 0)
		scrtmp.width = width;
	if (dgen_width >= 1)
		scrtmp.width = dgen_width;
	if (height != 0)
		scrtmp.height = height;
	if (dgen_height >= 1)
		scrtmp.height = dgen_height;
	if (dgen_depth >= 0) {
		scrtmp.bpp = dgen_depth;
		scrtmp.Bpp = 0;
	}
	// scrtmp.x_scale, scrtmp.y_scale and scrtmp.info_height cannot be
	// determined yet.
	scrtmp.want_fullscreen = !!dgen_fullscreen;
#ifdef WITH_OPENGL
opengl_failed:
	scrtmp.want_opengl = !!dgen_opengl;
#endif
#ifdef WITH_THREADS
	scrtmp.want_thread = !!dgen_screen_thread;
#endif
	// Configure SDL_SetVideoMode().
	if (scrtmp.want_fullscreen)
		flags |= SDL_FULLSCREEN;
#ifdef WITH_OPENGL
	if (scrtmp.want_opengl) {
		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
		SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, !!dgen_doublebuffer);
		flags |= SDL_OPENGL;
	}
	else
#endif
		flags |= ((dgen_doublebuffer ? SDL_DOUBLEBUF : 0) |
			  SDL_ASYNCBLIT);
	if (scrtmp.want_fullscreen) {
		SDL_Rect **modes;

		// Check if we're going to be bound to a particular resolution.
		modes = SDL_ListModes(NULL, (flags | SDL_FULLSCREEN));
		if ((modes != NULL) && (modes != (SDL_Rect **)-1)) {
			unsigned int i;
			struct {
				unsigned int i;
				unsigned int w;
				unsigned int h;
			} best = { 0, (unsigned int)-1, (unsigned int)-1 };

			// Find the best resolution available.
			for (i = 0; (modes[i] != NULL); ++i) {
				unsigned int w, h;

				DEBUG(("checking mode %dx%d",
				       modes[i]->w, modes[i]->h));
				if ((modes[i]->w < scrtmp.width) ||
				    (modes[i]->h < scrtmp.height))
					continue;
				w = (modes[i]->w - scrtmp.width);
				h = (modes[i]->h - scrtmp.height);
				if ((w <= best.w) && (h <= best.h)) {
					best.i = i;
					best.w = w;
					best.h = h;
				}
			}
			if ((best.w == (unsigned int)-1) ||
			    (best.h == (unsigned int)-1))
				DEBUG(("no mode looks good"));
			else {
				scrtmp.width = modes[best.i]->w;
				scrtmp.height = modes[best.i]->h;
				DEBUG(("mode %ux%u looks okay",
				       scrtmp.width, scrtmp.height));
			}
		}
		DEBUG(("adjusted fullscreen resolution to %ux%u",
		       scrtmp.width, scrtmp.height));
	}
	// Set video mode.
	DEBUG(("SDL_SetVideoMode(%u, %u, %d, 0x%08x)",
	       scrtmp.width, scrtmp.height, scrtmp.bpp, flags));
	scrtmp.surface = SDL_SetVideoMode(scrtmp.width, scrtmp.height,
					  scrtmp.bpp, flags);
	if (scrtmp.surface == NULL) {
#ifdef WITH_OPENGL
		// Try again without OpenGL.
		if (flags & SDL_OPENGL) {
			assert(scrtmp.want_opengl);
			DEBUG(("OpenGL initialization failed, retrying"
			       " without it."));
			dgen_opengl = 0;
			flags &= ~SDL_OPENGL;
			goto opengl_failed;
		}
#endif
		return -1;
	}
	DEBUG(("SDL_SetVideoMode succeeded"));
	// Update with current values.
	scrtmp.window_width = scrtmp.surface->w;
	scrtmp.window_height = scrtmp.surface->h;
	scrtmp.width = scrtmp.window_width;
	scrtmp.height = scrtmp.window_height;
	// By default, using 5% of the vertical resolution for info bar ought
	// to be good enough for anybody. Pick something close.
	if (dgen_info_height < 0)
		scrtmp.info_height = ((scrtmp.height * 5) / 100);
	else
		scrtmp.info_height = dgen_info_height;
	if (scrtmp.info_height > scrtmp.height)
		scrtmp.info_height = scrtmp.height;
	font = font_select(scrtmp.width, scrtmp.info_height, FONT_TYPE_AUTO);
	if (font == NULL)
		scrtmp.info_height = 0;
	else
		scrtmp.info_height = font->h;
	assert(scrtmp.info_height <= scrtmp.height); // Do not forget.
	// Determine default X and Y scale values from what remains.
	if (dgen_x_scale >= 0)
		scrtmp.x_scale = dgen_x_scale;
	else
		scrtmp.x_scale = (scrtmp.width / video.width);
	if (dgen_y_scale >= 0)
		scrtmp.y_scale = dgen_y_scale;
	else
		scrtmp.y_scale = ((scrtmp.height - scrtmp.info_height) /
				  video.height);
	if (dgen_aspect) {
		if (scrtmp.x_scale >= scrtmp.y_scale)
			scrtmp.x_scale = scrtmp.y_scale;
		else
			scrtmp.y_scale = scrtmp.x_scale;
	}
	// Fix bpp.
	assert(scrtmp.surface->format != NULL);
	scrtmp.bpp = scrtmp.surface->format->BitsPerPixel;
	// 15 bpp has be forced if it was required. SDL does not return the
	// right value.
	if ((dgen_depth == 15) && (scrtmp.bpp == 16))
		scrtmp.bpp = 15;
	scrtmp.Bpp = scrtmp.surface->format->BytesPerPixel;
	scrtmp.buf.u8 = (uint8_t *)scrtmp.surface->pixels;
	scrtmp.pitch = scrtmp.surface->pitch;
	scrtmp.is_fullscreen = scrtmp.want_fullscreen;
	DEBUG(("video configuration: x_scale=%u y_scale=%u",
	       scrtmp.x_scale, scrtmp.y_scale));
	DEBUG(("screen configuration: width=%u height=%u bpp=%u Bpp=%u"
	       " info_height=%u"
	       " buf.u8=%p pitch=%u surface=%p want_fullscreen=%u"
	       " is_fullscreen=%u",
	       scrtmp.width, scrtmp.height, scrtmp.bpp, scrtmp.Bpp,
	       scrtmp.info_height,
	       (void *)scrtmp.buf.u8, scrtmp.pitch, (void *)scrtmp.surface,
	       scrtmp.want_fullscreen, scrtmp.is_fullscreen));
#ifdef WITH_OPENGL
	if (scrtmp.want_opengl) {
		if (init_texture(&scrtmp)) {
			DEBUG(("OpenGL initialization failed, retrying"
			       " without it."));
			dgen_opengl = 0;
			flags &= ~SDL_OPENGL;
			goto opengl_failed;
		}
		// Update using texture info.
		scrtmp.Bpp = (2 << scrtmp.texture.u32);
		scrtmp.bpp = (scrtmp.Bpp * 8);
		scrtmp.buf.u32 = scrtmp.texture.buf.u32;
		scrtmp.width = scrtmp.texture.vis_width;
		scrtmp.height = scrtmp.texture.vis_height;
		scrtmp.pitch = (scrtmp.texture.vis_width <<
				(1 << scrtmp.texture.u32));
	}
	scrtmp.is_opengl = scrtmp.want_opengl;
	DEBUG(("OpenGL screen configuration: is_opengl=%u buf.u32=%p pitch=%u",
	       scrtmp.is_opengl, (void *)scrtmp.buf.u32, scrtmp.pitch));
#endif
	// Screen is now initialized, update data.
	screen = scrtmp;
#ifdef WITH_OPENGL
	if (!screen.is_opengl) {
		// Free OpenGL resources.
		DEBUG(("releasing OpenGL resources"));
		release_texture(screen.texture);
	}
#endif
	// Set up the Mega Drive screen.
	// Could not be done earlier because bpp was unknown.
	if ((mdscr.data == NULL) ||
	    ((unsigned int)mdscr.bpp != screen.bpp) ||
	    ((unsigned int)mdscr.w != (video.width + 16)) ||
	    ((unsigned int)mdscr.h != (video.height + 16))) {
		mdscr.w = (video.width + 16);
		mdscr.h = (video.height + 16);
		mdscr.pitch = (mdscr.w * screen.Bpp);
		mdscr.bpp = screen.bpp;
		free(mdscr.data);
		mdscr.data = (uint8_t *)calloc(mdscr.h, mdscr.pitch);
		if (mdscr.data == NULL) {
			// Cannot recover. Clean up and bail out.
			memset(&mdscr, 0, sizeof(mdscr));
			return -2;
		}
		mdscr_splash();
	}
	DEBUG(("md screen configuration: w=%d h=%d bpp=%d pitch=%d data=%p",
	       mdscr.w, mdscr.h, mdscr.bpp, mdscr.pitch, (void *)mdscr.data));
	// If we're in 8 bit mode, set color 0xff to white for the text,
	// and make a palette buffer.
	if (screen.bpp == 8) {
		SDL_Color color = { 0xff, 0xff, 0xff, 0x00 };

		SDL_SetColors(screen.surface, &color, 0xff, 1);
		memset(video.palette, 0x00, sizeof(video.palette));
		mdpal = video.palette;
	}
	else
		mdpal = NULL;
#ifdef WITH_THREADS
	if (screen.want_thread)
		screen_update_thread_start();
#endif
	// Rehash filters.
	filters_stack_update();
	// Update screen.
	pd_graphics_update(true);
	return 0;
}

/**
 * Set fullscreen mode.
 * @param toggle Nonzero to enable fullscreen, otherwise disable it.
 * @return 0 on success.
 */
static int set_fullscreen(int toggle)
{
	unsigned int w;
	unsigned int h;

	if (((!toggle) && (!screen.is_fullscreen)) ||
	    ((toggle) && (screen.is_fullscreen))) {
		// Already in the desired mode.
		DEBUG(("already %s fullscreen mode, ret=-1",
		       (toggle ? "in" : "not in")));
		return -1;
	}
#ifdef HAVE_SDL_WM_TOGGLEFULLSCREEN
	// Try this first.
	DEBUG(("trying SDL_WM_ToggleFullScreen(%p)", (void *)screen.surface));
	if (SDL_WM_ToggleFullScreen(screen.surface))
		return 0;
	DEBUG(("falling back to screen_init()"));
#endif
	dgen_fullscreen = toggle;
	if (screen.surface != NULL) {
		// Try to keep the current mode.
		w = screen.surface->w;
		h = screen.surface->h;
	}
	else if ((dgen_width > 0) && (dgen_height > 0)) {
		// Use configured mode.
		w = dgen_width;
		h = dgen_height;
	}
	else {
		// Try to make a guess.
		w = (video.width * screen.x_scale);
		h = (video.height * screen.y_scale);
	}
	DEBUG(("reinitializing screen with want_fullscreen=%u,"
	       " screen_init(%u, %u)",
	       screen.want_fullscreen, w, h));
	return screen_init(w, h);
}

/**
 * Initialize SDL, and the graphics.
 * @param want_sound Nonzero if we want sound.
 * @param want_pal Nonzero for PAL mode.
 * @param hz Requested frame rate (between 0 and 1000).
 * @return Nonzero if successful.
 */
int pd_graphics_init(int want_sound, int want_pal, int hz)
{
	SDL_Event event;

	prompt_init(&prompt.status);
	if ((hz <= 0) || (hz > 1000)) {
		// You may as well disable bool_frameskip.
		fprintf(stderr, "sdl: invalid frame rate (%d)\n", hz);
		return 0;
	}
	video.hz = hz;
	if (want_pal) {
		// PAL
		video.is_pal = 1;
		video.height = 240;
	}
	else {
		// NTSC
		video.is_pal = 0;
		video.height = 224;
	}
#ifndef __MINGW32__
	// [fbcon workaround]
	// Disable SDL_FBACCEL (if unset) before calling SDL_Init() in case
	// fbcon is to be used. Prevents SDL_FillRect() and SDL_SetVideoMode()
	// from hanging when hardware acceleration is available.
	setenv("SDL_FBACCEL", "0", 0);
	// [fbcon workaround]
	// A mouse is never required.
	setenv("SDL_NOMOUSE", "1", 0);
#endif
	if (SDL_Init(SDL_INIT_VIDEO | (want_sound ? SDL_INIT_AUDIO : 0))) {
		fprintf(stderr, "sdl: can't init SDL: %s\n", SDL_GetError());
		return 0;
	}
#ifndef __MINGW32__
	{
		char buf[32];

		// [fbcon workaround]
		// Double buffering usually makes screen blink during refresh.
		if ((SDL_VideoDriverName(buf, sizeof(buf))) &&
		    (!strcmp(buf, "fbcon")))
			dgen_doublebuffer = 0;
	}
#endif
	// Required for text input.
	SDL_EnableUNICODE(1);
	// Set the titlebar.
	SDL_WM_SetCaption("DGen/SDL " VER, "DGen/SDL " VER);
	// Hide the cursor.
	SDL_ShowCursor(0);
	// Initialize screen.
	if (screen_init(0, 0))
		goto fail;
	// Initialize scaling.
	set_scaling(scaling_names[dgen_scaling % NUM_SCALING]);
	DEBUG(("using scaling filter \"%s\"",
	       scaling_names[dgen_scaling % NUM_SCALING]));
	DEBUG(("screen initialized"));
#ifndef __MINGW32__
	// We don't need setuid privileges anymore
	if (getuid() != geteuid())
		setuid(getuid());
	DEBUG(("setuid privileges dropped"));
#endif
#ifdef WITH_CTV
	filters_pluck_ctv();
	{
		const struct filter *f;

		f = filters_find(ctv_names[dgen_craptv % NUM_CTV]);
		if ((f != NULL) && (f->func != filter_off))
			filters_insert(f);
	}
#endif // WITH_CTV
	DEBUG(("ret=1"));
	fprintf(stderr, "video: %dx%d, %u bpp (%u Bpp), %uHz\n",
		screen.surface->w, screen.surface->h, screen.bpp,
		screen.Bpp, video.hz);
#ifdef WITH_OPENGL
	if (screen.is_opengl) {
		DEBUG(("GL_VENDOR=\"%s\" GL_RENDERER=\"%s\""
		       " GL_VERSION=\"%s\"",
		       glGetString(GL_VENDOR), glGetString(GL_RENDERER),
		       glGetString(GL_VERSION)));
		fprintf(stderr,
			"video: OpenGL texture %ux%ux%u (%ux%u)\n",
			screen.texture.width,
			screen.texture.height,
			(2 << screen.texture.u32),
			screen.texture.vis_width,
			screen.texture.vis_height);
	}
#endif
	while (SDL_PollEvent(&event)) {
		switch (event.type) {
		case SDL_VIDEORESIZE:
			if (screen_init(event.resize.w, event.resize.h))
				goto fail;
			break;
		}
	}
	return 1;
fail:
	fprintf(stderr, "sdl: can't initialize graphics.\n");
	return 0;
}

/**
 * Reinitialize graphics.
 * @param want_pal Nonzero for PAL mode.
 * @param hz Requested frame rate (between 0 and 1000).
 * @return Nonzero if successful.
 */
int pd_graphics_reinit(int, int want_pal, int hz)
{
	if ((hz <= 0) || (hz > 1000)) {
		// You may as well disable bool_frameskip.
		fprintf(stderr, "sdl: invalid frame rate (%d)\n", hz);
		return 0;
	}
	video.hz = hz;
	if (want_pal) {
		// PAL
		video.is_pal = 1;
		video.height = 240;
	}
	else {
		// NTSC
		video.is_pal = 0;
		video.height = 224;
	}
	// Reinitialize screen.
	if (screen_init(screen.window_width, screen.window_height))
		goto fail;
	DEBUG(("screen reinitialized"));
	return 1;
fail:
	fprintf(stderr, "sdl: can't reinitialize graphics.\n");
	return 0;
}

/**
 * Update palette.
 */
void pd_graphics_palette_update()
{
	unsigned int i;

	for (i = 0; (i < 64); ++i) {
		screen.color[i].r = mdpal[(i << 2)];
		screen.color[i].g = mdpal[((i << 2) + 1)];
		screen.color[i].b = mdpal[((i << 2) + 2)];
	}
#ifdef WITH_OPENGL
	if (!screen.is_opengl)
#endif
		SDL_SetColors(screen.surface, screen.color, 0, 64);
}

/**
 * Display screen.
 * @param update False if screen buffer is garbage and must be updated first.
 */
void pd_graphics_update(bool update)
{
	static unsigned long fps_since = 0;
	static unsigned long frames_old = 0;
	static unsigned long frames = 0;
	unsigned long usecs = pd_usecs();
	const struct filter *f;
	struct filter_data *fd;
	size_t i;

	// Check whether the message must be processed.
	if ((events == STARTED) &&
	    ((info.displayed) || (info.length))  &&
	    ((usecs - info.since) >= MESSAGE_LIFE))
		pd_message_process();
	else if (dgen_fps) {
		unsigned long tmp = ((usecs - fps_since) & 0x3fffff);

		++frames;
		if (tmp >= 1000000) {
			unsigned long fps;

			fps_since = usecs;
			if (frames_old > frames)
				fps = (frames_old - frames);
			else
				fps = (frames - frames_old);
			frames_old = frames;
			if (!info.displayed) {
				char buf[16];

				snprintf(buf, sizeof(buf), "%lu FPS", fps);
				pd_message_write(buf, strlen(buf), ~0u);
			}
		}
	}
	if (update == false)
		mdscr_splash();
	// Process output through filters.
	for (i = 0; (i != elemof(filters_stack)); ++i) {
		f = filters_stack[i];
		fd = &filters_stack_data[i];
		if ((filters_stack_size == 0) ||
		    (i == (filters_stack_size - 1)))
			break;
		f->func(fd, (fd + 1));
	}
	// Lock screen.
	if (screen_lock())
		return;
	// Generate screen output with the last filter.
	f->func(fd, (fd + 1));
	// Unlock screen.
	screen_unlock();
	// Update the screen.
	screen_update();
}

/**
 * Callback for sound.
 * @param stream Sound destination buffer.
 * @param len Length of destination buffer.
 */
static void snd_callback(void *, Uint8 *stream, int len)
{
	size_t wrote;

	// Slurp off the play buffer
	wrote = cbuf_read(stream, &sound.cbuf, len);
	if (wrote == (size_t)len)
		return;
	// Not enough data, fill remaining space with silence.
	memset(&stream[wrote], 0, ((size_t)len - wrote));
}

/**
 * Initialize the sound.
 * @param freq Sound samples rate.
 * @param[in,out] samples Minimum buffer size in samples.
 * @return Nonzero on success.
 */
int pd_sound_init(long &freq, unsigned int &samples)
{
	SDL_AudioSpec wanted;
	SDL_AudioSpec spec;

	// Clean up first.
	pd_sound_deinit();

	// Set the desired format
	wanted.freq = freq;
#ifdef WORDS_BIGENDIAN
	wanted.format = AUDIO_S16MSB;
#else
	wanted.format = AUDIO_S16LSB;
#endif
	wanted.channels = 2;
	wanted.samples = dgen_soundsamples;
	wanted.callback = snd_callback;
	wanted.userdata = NULL;

	if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
		fprintf(stderr, "sdl: unable to initialize audio\n");
		return 0;
	}

	// Open audio, and get the real spec
	if (SDL_OpenAudio(&wanted, &spec) < 0) {
		fprintf(stderr,
			"sdl: couldn't open audio: %s\n",
			SDL_GetError());
		return 0;
	}

	// Check everything
	if (spec.channels != 2) {
		fprintf(stderr, "sdl: couldn't get stereo audio format.\n");
		goto snd_error;
	}
	if (spec.format != wanted.format) {
		fprintf(stderr, "sdl: unable to get 16-bit audio.\n");
		goto snd_error;
	}

	// Set things as they really are
	sound.rate = freq = spec.freq;
	sndi.len = (spec.freq / video.hz);
	sound.samples = spec.samples;
	samples += sound.samples;

	// Calculate buffer size (sample size = (channels * (bits / 8))).
	sound.cbuf.size = (samples * (2 * (16 / 8)));
	sound.cbuf.i = 0;
	sound.cbuf.s = 0;

	fprintf(stderr, "sound: %uHz, %d samples, buffer: %u bytes\n",
		sound.rate, spec.samples, (unsigned int)sound.cbuf.size);

	// Allocate zero-filled play buffer.
	sndi.lr = (int16_t *)calloc(2, (sndi.len * sizeof(sndi.lr[0])));

	sound.cbuf.data.i16 = (int16_t *)calloc(1, sound.cbuf.size);
	if ((sndi.lr == NULL) || (sound.cbuf.data.i16 == NULL)) {
		fprintf(stderr, "sdl: couldn't allocate sound buffers.\n");
		goto snd_error;
	}

	// Start sound output.
	SDL_PauseAudio(0);

	// It's all good!
	return 1;

snd_error:
	// Oops! Something bad happened, cleanup.
	SDL_CloseAudio();
	free((void *)sndi.lr);
	sndi.lr = NULL;
	sndi.len = 0;
	free((void *)sound.cbuf.data.i16);
	sound.cbuf.data.i16 = NULL;
	memset(&sound, 0, sizeof(sound));
	return 0;
}

/**
 * Deinitialize sound subsystem.
 */
void pd_sound_deinit()
{
	if (sound.cbuf.data.i16 != NULL) {
		SDL_PauseAudio(1);
		SDL_CloseAudio();
		free((void *)sound.cbuf.data.i16);
	}
	memset(&sound, 0, sizeof(sound));
	free((void*)sndi.lr);
	sndi.lr = NULL;
}

/**
 * Return samples read/write indices in the buffer.
 */
unsigned int pd_sound_rp()
{
	unsigned int ret;

	if (!sound.cbuf.size)
		return 0;
	SDL_LockAudio();
	ret = sound.cbuf.i;
	SDL_UnlockAudio();
	return (ret >> 2);
}

unsigned int pd_sound_wp()
{
	unsigned int ret;

	if (!sound.cbuf.size)
		return 0;
	SDL_LockAudio();
	ret = ((sound.cbuf.i + sound.cbuf.s) % sound.cbuf.size);
	SDL_UnlockAudio();
	return (ret >> 2);
}

/**
 * Write contents of sndi to sound.cbuf.
 */
void pd_sound_write()
{
	if (!sound.cbuf.size)
		return;
	SDL_LockAudio();
	cbuf_write(&sound.cbuf, (uint8_t *)sndi.lr, (sndi.len * 4));
	SDL_UnlockAudio();
}

/**
 * Tells whether DGen stopped intentionally so emulation can resume without
 * skipping frames.
 */
int pd_stopped()
{
	int ret = stopped;

	stopped = 0;
	return ret;
}

/**
 * Keyboard input.
 */
typedef struct {
	char *buf;
	size_t pos;
	size_t size;
} kb_input_t;

/**
 * Keyboard input results.
 */
enum kb_input {
	KB_INPUT_ABORTED,
	KB_INPUT_ENTERED,
	KB_INPUT_CONSUMED,
	KB_INPUT_IGNORED
};

/**
 * Manage text input with some rudimentary history.
 * @param input Input buffer.
 * @param ksym Keyboard symbol.
 * @param ksym_uni Unicode translation for keyboard symbol.
 * @return Input result.
 */
static enum kb_input kb_input(kb_input_t *input, uint32_t ksym,
			      uint16_t ksym_uni)
{
#define HISTORY_LEN 32
	static char history[HISTORY_LEN][64];
	static int history_pos = -1;
	static int history_len = 0;
	char c;

	if (ksym & KEYSYM_MOD_CTRL)
		return KB_INPUT_IGNORED;
	if (isprint((c = ksym_uni))) {
		if (input->pos >= (input->size - 1))
			return KB_INPUT_CONSUMED;
		if (input->buf[input->pos] == '\0')
			input->buf[(input->pos + 1)] = '\0';
		input->buf[input->pos] = c;
		++input->pos;
		return KB_INPUT_CONSUMED;
	}
	else if (ksym == SDLK_DELETE) {
		size_t tail;

		if (input->buf[input->pos] == '\0')
			return KB_INPUT_CONSUMED;
		tail = ((input->size - input->pos) + 1);
		memmove(&input->buf[input->pos],
			&input->buf[(input->pos + 1)],
			tail);
		return KB_INPUT_CONSUMED;
	}
	else if (ksym == SDLK_BACKSPACE) {
		size_t tail;

		if (input->pos == 0)
			return KB_INPUT_CONSUMED;
		--input->pos;
		tail = ((input->size - input->pos) + 1);
		memmove(&input->buf[input->pos],
			&input->buf[(input->pos + 1)],
			tail);
		return KB_INPUT_CONSUMED;
	}
	else if (ksym == SDLK_LEFT) {
		if (input->pos != 0)
			--input->pos;
		return KB_INPUT_CONSUMED;
	}
	else if (ksym == SDLK_RIGHT) {
		if (input->buf[input->pos] != '\0')
			++input->pos;
		return KB_INPUT_CONSUMED;
	}
	else if ((ksym == SDLK_RETURN) || (ksym == SDLK_KP_ENTER)) {
		history_pos = -1;
		if (input->pos == 0)
			return KB_INPUT_ABORTED;
		if (history_len < HISTORY_LEN)
			++history_len;
		memmove(&history[1], &history[0],
			((history_len - 1) * sizeof(history[0])));
		strncpy(history[0], input->buf, sizeof(history[0]));
		return KB_INPUT_ENTERED;
	}
	else if (ksym == SDLK_ESCAPE) {
		history_pos = 0;
		return KB_INPUT_ABORTED;
	}
	else if (ksym == SDLK_UP) {
		if (input->size == 0)
			return KB_INPUT_CONSUMED;
		if (history_pos < (history_len - 1))
			++history_pos;
		strncpy(input->buf, history[history_pos], input->size);
		input->buf[(input->size - 1)] = '\0';
		input->pos = strlen(input->buf);
		return KB_INPUT_CONSUMED;
	}
	else if (ksym == SDLK_DOWN) {
		if ((input->size == 0) || (history_pos < 0))
			return KB_INPUT_CONSUMED;
		if (history_pos > 0)
			--history_pos;
		strncpy(input->buf, history[history_pos], input->size);
		input->buf[(input->size - 1)] = '\0';
		input->pos = strlen(input->buf);
		return KB_INPUT_CONSUMED;
	}
	return KB_INPUT_IGNORED;
}

/**
 * Write a message to the status bar while displaying a cursor and without
 * buffering.
 */
static void pd_message_cursor(unsigned int mark, const char *msg, ...)
{
	va_list vl;
	char buf[1024];
	size_t len;
	size_t disp_len;

	va_start(vl, msg);
	len = (size_t)vsnprintf(buf, sizeof(buf), msg, vl);
	va_end(vl);
	buf[(sizeof(buf) - 1)] = '\0';
	disp_len = font_text_max_len(screen.width, screen.info_height,
				     FONT_TYPE_AUTO);
	if (mark > len) {
		if (len <= disp_len)
			pd_message_display(buf, len, ~0u, true);
		else
			pd_message_display(&(buf[(len - disp_len)]), disp_len,
					   ~0u, true);
		return;
	}
	if (len <= disp_len)
		pd_message_display(buf, len, mark, true);
	else if (len == mark)
		pd_message_display(&buf[((len - disp_len) + 1)],
				   disp_len, (disp_len - 1), true);
	else if ((len - mark) < disp_len)
		pd_message_display(&buf[(len - disp_len)], disp_len,
				   (mark - (len - disp_len)), true);
	else if (mark != ~0u)
		pd_message_display(&buf[mark], disp_len, 0, true);
}

/**
 * Rehash rc vars that require special handling (see "SH" in rc.cpp).
 */
static int prompt_rehash_rc_field(const struct rc_field *rc, md& megad)
{
	bool fail = false;
	bool init_video = false;
	bool init_sound = false;
	bool init_joystick = false;

	if (rc->variable == &dgen_craptv) {
#ifdef WITH_CTV
		filters_pluck_ctv();
		filters_insert(filters_find(ctv_names[dgen_craptv % NUM_CTV]));
#else
		fail = true;
#endif
	}
	else if (rc->variable == &dgen_scaling) {
		if (set_scaling(scaling_names[dgen_scaling]) == 0)
			fail = true;
	}
	else if (rc->variable == &dgen_emu_z80) {
		megad.z80_state_dump();
		// Z80: 0 = none, 1 = CZ80, 2 = MZ80, 3 = DRZ80
		switch (dgen_emu_z80) {
#ifdef WITH_MZ80
		case 1:
			megad.z80_core = md::Z80_CORE_MZ80;
			break;
#endif
#ifdef WITH_CZ80
		case 2:
			megad.z80_core = md::Z80_CORE_CZ80;
			break;
#endif
#ifdef WITH_DRZ80
		case 3:
			megad.z80_core = md::Z80_CORE_DRZ80;
			break;
#endif
		default:
			megad.z80_core = md::Z80_CORE_NONE;
			break;
		}
		megad.z80_state_restore();
	}
	else if (rc->variable == &dgen_emu_m68k) {
		megad.m68k_state_dump();
		// M68K: 0 = none, 1 = StarScream, 2 = Musashi, 3 = Cyclone
		switch (dgen_emu_m68k) {
#ifdef WITH_STAR
		case 1:
			megad.cpu_emu = md::CPU_EMU_STAR;
			break;
#endif
#ifdef WITH_MUSA
		case 2:
			megad.cpu_emu = md::CPU_EMU_MUSA;
			break;
#endif
#ifdef WITH_CYCLONE
		case 3:
			megad.cpu_emu = md::CPU_EMU_CYCLONE;
			break;
#endif
		default:
			megad.cpu_emu = md::CPU_EMU_NONE;
			break;
		}
		megad.m68k_state_restore();
	}
	else if ((rc->variable == &dgen_sound) ||
		 (rc->variable == &dgen_soundrate) ||
		 (rc->variable == &dgen_soundsegs) ||
		 (rc->variable == &dgen_soundsamples) ||
		 (rc->variable == &dgen_mjazz))
		init_sound = true;
	else if (rc->variable == &dgen_fullscreen) {
		if (screen.want_fullscreen != (!!dgen_fullscreen)) {
			init_video = true;
		}
	}
	else if ((rc->variable == &dgen_info_height) ||
		 (rc->variable == &dgen_width) ||
		 (rc->variable == &dgen_height) ||
		 (rc->variable == &dgen_x_scale) ||
		 (rc->variable == &dgen_y_scale) ||
		 (rc->variable == &dgen_depth) ||
		 (rc->variable == &dgen_doublebuffer) ||
		 (rc->variable == &dgen_screen_thread))
		init_video = true;
	else if (rc->variable == &dgen_swab) {
#ifdef WITH_CTV
		set_swab();
#else
		fail = true;
#endif
	}
	else if ((rc->variable == &dgen_scale) ||
		 (rc->variable == &dgen_aspect)) {
		dgen_x_scale = dgen_scale;
		dgen_y_scale = dgen_scale;
		init_video = true;
	}
	else if ((rc->variable == &dgen_opengl) ||
		 (rc->variable == &dgen_opengl_stretch) ||
		 (rc->variable == &dgen_opengl_linear) ||
		 (rc->variable == &dgen_opengl_32bit) ||
		 (rc->variable == &dgen_opengl_square)) {
#ifdef WITH_OPENGL
		init_video = true;
#else
		(void)0;
#endif
	}
	else if (rc->variable == &dgen_joystick)
		init_joystick = true;
	else if (rc->variable == &dgen_hz) {
		// See md::md().
		if (dgen_hz <= 0)
			dgen_hz = 1;
		else if (dgen_hz > 1000)
			dgen_hz = 1000;
		if (((unsigned int)dgen_hz != video.hz) ||
		    ((unsigned int)dgen_hz != megad.vhz)) {
			video.hz = dgen_hz;
			init_video = true;
			init_sound = true;
		}
	}
	else if (rc->variable == &dgen_pal) {
		// See md::md().
		if ((dgen_pal) &&
		    ((video.is_pal == false) ||
		     (megad.pal == 0) ||
		     (video.height != PAL_VBLANK))) {
			megad.pal = 1;
			megad.init_pal();
			video.is_pal = true;
			video.height = PAL_VBLANK;
			init_video = true;
		}
		else if ((!dgen_pal) &&
			 ((video.is_pal == true) ||
			  (megad.pal == 1) ||
			  (video.height != NTSC_VBLANK))) {
			megad.pal = 0;
			megad.init_pal();
			video.is_pal = false;
			video.height = NTSC_VBLANK;
			init_video = true;
		}
	}
	else if (rc->variable == &dgen_region) {
		uint8_t c;
		int hz;
		int pal;
		int vblank;

		if (dgen_region)
			c = dgen_region;
		else
			c = megad.region_guess();
		md::region_info(c, &pal, &hz, &vblank, 0, 0);
		if ((hz != dgen_hz) || (pal != dgen_pal) ||
		    (c != megad.region)) {
			megad.region = c;
			dgen_hz = hz;
			dgen_pal = pal;
			megad.pal = pal;
			megad.init_pal();
			video.is_pal = pal;
			video.height = vblank;
			video.hz = hz;
			init_video = true;
			init_sound = true;
			fprintf(stderr,
				"sdl: reconfiguring for region \"%c\": "
				"%dHz (%s)\n", megad.region, hz,
				(pal ? "PAL" : "NTSC"));
		}
	}
	else if (rc->variable == (intptr_t *)((void *)&dgen_rom_path))
		set_rom_path(dgen_rom_path.val);
	if (init_video) {
		// This is essentially what pd_graphics_init() does.
		memset(megad.vdp.dirt, 0xff, 0x35);
		switch (screen_init(screen.window_width,
				    screen.window_height)) {
		case 0:
			break;
		case -1:
			goto video_warn;
		default:
			goto video_fail;
		}
	}
	if (init_sound) {
		if (video.hz == 0)
			fail = true;
		else if (dgen_sound == 0)
			pd_sound_deinit();
		else {
			uint8_t ym2612_buf[512];
			uint8_t sn76496_buf[16];
			unsigned int samples;
			long rate = dgen_soundrate;

			pd_sound_deinit();
			samples = (dgen_soundsegs * (rate / video.hz));
			if (!pd_sound_init(rate, samples))
				fail = true;
			YM2612_dump(0, ym2612_buf);
			SN76496_dump(0, sn76496_buf);
			megad.init_sound();
			SN76496_restore(0, sn76496_buf);
			YM2612_restore(0, ym2612_buf);
		}
	}
	if (init_joystick) {
#ifdef WITH_JOYSTICK
		megad.deinit_joysticks();
		if (dgen_joystick)
			megad.init_joysticks();
#else
		fail = true;
#endif
	}
	if (fail) {
		pd_message("Failed to rehash value.");
		return (PROMPT_RET_EXIT | PROMPT_RET_MSG);
	}
	return PROMPT_RET_CONT;
video_warn:
	pd_message("Failed to reinitialize video.");
	return (PROMPT_RET_EXIT | PROMPT_RET_MSG);
video_fail:
	fprintf(stderr, "sdl: fatal error while trying to change screen"
		" resolution.\n");
	return (PROMPT_RET_ERROR | PROMPT_RET_MSG);
}

static void prompt_show_rc_field(const struct rc_field *rc)
{
	size_t i;
	intptr_t val = *rc->variable;

	if ((rc->parser == rc_number) ||
	    (rc->parser == rc_soundrate))
		pd_message("%s is %ld", rc->fieldname, val);
	else if (rc->parser == rc_keysym) {
		char *ks = dump_keysym(val);

		if ((ks == NULL) || (ks[0] == '\0'))
			pd_message("%s isn't bound", rc->fieldname);
		else
			pd_message("%s is bound to \"%s\"", rc->fieldname, ks);
		free(ks);
	}
	else if (rc->parser == rc_boolean)
		pd_message("%s is %s", rc->fieldname,
			   ((val) ? "true" : "false"));
	else if (rc->parser == rc_joypad) {
		char *js = dump_joypad(val);

		if ((js == NULL) || (js[0] == '\0'))
			pd_message("%s isn't bound", rc->fieldname);
		else
			pd_message("%s is bound to \"%s\"", rc->fieldname, js);
		free(js);
	}
	else if (rc->parser == rc_mouse) {
		char *mo = dump_mouse(val);

		if ((mo == NULL) || (mo[0] == '\0'))
			pd_message("%s isn't bound", rc->fieldname);
		else
			pd_message("%s is bound to \"%s\"", rc->fieldname, mo);
		free(mo);
	}
	else if (rc->parser == rc_ctv) {
		i = val;
		if (i >= NUM_CTV)
			pd_message("%s is undefined", rc->fieldname);
		else
			pd_message("%s is \"%s\"", rc->fieldname,
				   ctv_names[i]);
	}
	else if (rc->parser == rc_scaling) {
		i = val;
		if (i >= NUM_SCALING)
			pd_message("%s is undefined", rc->fieldname);
		else
			pd_message("%s is \"%s\"", rc->fieldname,
				   scaling_names[i]);
	}
	else if (rc->parser == rc_emu_z80) {
		for (i = 0; (emu_z80_names[i] != NULL); ++i)
			if (i == (size_t)val)
				break;
		if (emu_z80_names[i] == NULL)
			pd_message("%s is undefined", rc->fieldname);
		else
			pd_message("%s is \"%s\"", rc->fieldname,
				   emu_z80_names[i]);
	}
	else if (rc->parser == rc_emu_m68k) {
		for (i = 0; (emu_m68k_names[i] != NULL); ++i)
			if (i == (size_t)val)
				break;
		if (emu_m68k_names[i] == NULL)
			pd_message("%s is undefined", rc->fieldname);
		else
			pd_message("%s is \"%s\"", rc->fieldname,
				   emu_m68k_names[i]);
	}
	else if (rc->parser == rc_region) {
		const char *s;

		if (val == 'U')
			s = "America (NTSC)";
		else if (val == 'E')
			s = "Europe (PAL)";
		else if (val == 'J')
			s = "Japan (NTSC)";
		else if (val == 'X')
			s = "Japan (PAL)";
		else
			s = "Auto";
		pd_message("%s is \"%c\" (%s)", rc->fieldname,
			   (val ? (char)val : (char)' '), s);
	}
	else if ((rc->parser == rc_string) ||
		 (rc->parser == rc_rom_path)) {
		struct rc_str *rs = (struct rc_str *)rc->variable;
		char *s;

		if (rs->val == NULL)
			pd_message("%s has no value", rc->fieldname);
		else if ((s = backslashify((const uint8_t *)rs->val,
					   strlen(rs->val), 0,
					   NULL)) != NULL) {
			pd_message("%s is \"%s\"", rc->fieldname, s);
			free(s);
		}
		else
			pd_message("%s can't be displayed", rc->fieldname);
	}
	else if (rc->parser == rc_bind) {
		char *f = backslashify((uint8_t *)rc->fieldname,
				       strlen(rc->fieldname), 0, NULL);
		char *s = *(char **)rc->variable;

		assert(s != NULL);
		assert((intptr_t)s != -1);
		s = backslashify((uint8_t *)s, strlen(s), 0, NULL);
		if ((f == NULL) || (s == NULL))
			pd_message("%s can't be displayed", rc->fieldname);
		else
			pd_message("%s is bound to \"%s\"", f, s);
		free(f);
		free(s);
	}
	else
		pd_message("%s: can't display value", rc->fieldname);
}

static int handle_prompt_enter(class md& md)
{
	struct prompt_parse pp;
	struct prompt *p = &prompt.status;
	size_t i;
	int ret;
	bool binding_tried = false;

	if (prompt_parse(p, &pp) == NULL)
		return PROMPT_RET_ERROR;
	if (pp.argc == 0) {
		ret = PROMPT_RET_EXIT;
		goto end;
	}
	ret = 0;
	// Look for a command with that name.
	for (i = 0; (prompt_command[i].name != NULL); ++i) {
		int cret;

		if (strcasecmp(prompt_command[i].name, (char *)pp.argv[0]))
			continue;
		cret = prompt_command[i].cmd(md, pp.argc,
					     (const char **)pp.argv);
		if ((cret & ~CMD_MSG) == CMD_ERROR)
			ret |= PROMPT_RET_ERROR;
		if (cret & CMD_MSG)
			ret |= PROMPT_RET_MSG;
		else if (cret & CMD_FAIL) {
			pd_message("%s: command failed", (char *)pp.argv[0]);
			ret |= PROMPT_RET_MSG;
		}
		else if (cret & CMD_EINVAL) {
			pd_message("%s: invalid argument", (char *)pp.argv[0]);
			ret |= PROMPT_RET_MSG;
		}
		goto end;
	}
binding_retry:
	// Look for a variable with that name.
	for (i = 0; (rc_fields[i].fieldname != NULL); ++i) {
		intptr_t potential;

		if (strcasecmp(rc_fields[i].fieldname, (char *)pp.argv[0]))
			continue;
		// Display current value?
		if (pp.argv[1] == NULL) {
			prompt_show_rc_field(&rc_fields[i]);
			ret |= PROMPT_RET_MSG;
			break;
		}
		// Parse and set value.
		potential = rc_fields[i].parser((char *)pp.argv[1],
						rc_fields[i].variable);
		if ((rc_fields[i].parser != rc_number) && (potential == -1)) {
			pd_message("%s: invalid value",	(char *)pp.argv[0]);
			ret |= PROMPT_RET_MSG;
			break;
		}
		if ((rc_fields[i].parser == rc_string) ||
		    (rc_fields[i].parser == rc_rom_path)) {
			struct rc_str *rs;

			rs = (struct rc_str *)rc_fields[i].variable;
			if (rc_str_list == NULL) {
				atexit(rc_str_cleanup);
				rc_str_list = rs;
			}
			else if (rs->alloc == NULL) {
				rs->next = rc_str_list;
				rc_str_list = rs;
			}
			else
				free(rs->alloc);
			rs->alloc = (char *)potential;
			rs->val = rs->alloc;
		}
		else
			*(rc_fields[i].variable) = potential;
		ret |= prompt_rehash_rc_field(&rc_fields[i], md);
		break;
	}
	if (rc_fields[i].fieldname == NULL) {
		if ((binding_tried == false) &&
		    (rc_binding_add((char *)pp.argv[0], "") != NULL)) {
			binding_tried = true;
			goto binding_retry;
		}
		pd_message("%s: unknown command", (char *)pp.argv[0]);
		ret |= PROMPT_RET_MSG;
	}
end:
	prompt_parse_clean(&pp);
	prompt_push(p);
	ret |= PROMPT_RET_ENTER;
	return ret;
}

static void handle_prompt_complete_clear()
{
	complete_path_free(prompt.complete);
	prompt.complete = NULL;
	prompt.skip = 0;
	prompt.common = 0;
}

static int handle_prompt_complete(class md& md, bool rwd)
{
	struct prompt_parse pp;
	struct prompt *p = &prompt.status;
	size_t prompt_common = 0; // escaped version of prompt.common
	unsigned int skip;
	size_t i;
	const char *arg;
	unsigned int alen;
	char *s = NULL;

	if (prompt_parse(p, &pp) == NULL)
		return PROMPT_RET_ERROR;
	if (rwd)
		prompt.skip -= 2;
	if (pp.index == 0) {
		const char *cs = NULL;
		const char *cm = NULL;
		size_t common;
		unsigned int tmp;

		assert(prompt.complete == NULL);
		// The first argument needs to be completed. This is either
		// a command or a variable name.
		arg = (const char *)pp.argv[0];
		alen = pp.cursor;
		if ((arg == NULL) || (alen == ~0u)) {
			arg = "";
			alen = 0;
		}
		common = ~0u;
	complete_cmd_var:
		skip = prompt.skip;
		for (i = 0; (prompt_command[i].name != NULL); ++i) {
			if (strncasecmp(prompt_command[i].name, arg, alen))
				continue;
			if (cm == NULL)
				tmp = strlen(prompt_command[i].name);
			else
				tmp = strcommon(prompt_command[i].name, cm);
			cm = prompt_command[i].name;
			if (tmp < common)
				common = tmp;
			if (skip != 0) {
				--skip;
				continue;
			}
			if (cs == NULL)
				cs = prompt_command[i].name;
			if (common == 0)
				goto complete_cmd_found;
		}
		// Variables.
		for (i = 0; (rc_fields[i].fieldname != NULL); ++i) {
			if (strncasecmp(rc_fields[i].fieldname, arg, alen))
				continue;
			if (cm == NULL)
				tmp = strlen(rc_fields[i].fieldname);
			else
				tmp = strcommon(rc_fields[i].fieldname, cm);
			cm = rc_fields[i].fieldname;
			if (tmp < common)
				common = tmp;
			if (skip != 0) {
				--skip;
				continue;
			}
			if (cs == NULL)
				cs = rc_fields[i].fieldname;
			if (common == 0)
				break;
		}
		if (cs == NULL) {
			// Nothing matched, try again if possible.
			if (prompt.skip) {
				prompt.skip = 0;
				goto complete_cmd_var;
			}
			goto end;
		}
	complete_cmd_found:
		++prompt.skip;
		s = backslashify((const uint8_t *)cs, strlen(cs), 0, &common);
		if (s == NULL)
			goto end;
		if (common != ~0u) {
			prompt.common = common;
			prompt_common = common;
		}
		goto replace;
	}
	// Complete function arguments.
	for (i = 0; (prompt_command[i].name != NULL); ++i) {
		char *t;

		if (strcasecmp(prompt_command[i].name,
			       (const char *)pp.argv[0]))
			continue;
		if (prompt_command[i].cmpl == NULL)
			goto end;
		t = prompt_command[i].cmpl(md, pp.argc, (const char **)pp.argv,
					   pp.cursor);
		if (t == NULL)
			goto end;
		prompt_common = prompt.common;
		s = backslashify((const uint8_t *)t, strlen(t), 0,
				 &prompt_common);
		free(t);
		if (s == NULL)
			goto end;
		goto replace;
	}
	// Variable value completion.
	arg = (const char *)pp.argv[pp.index];
	alen = pp.cursor;
	if ((arg == NULL) || (alen == ~0u)) {
		arg = "";
		alen = 0;
	}
	for (i = 0; (rc_fields[i].fieldname != NULL); ++i) {
		struct rc_field *rc = &rc_fields[i];
		const char **names;

		if (strcasecmp(rc->fieldname, (const char *)pp.argv[0]))
			continue;
		// Boolean values.
		if (rc->parser == rc_boolean)
			s = strdup((prompt.skip & 1) ? "true" : "false");
		// ROM path.
		else if (rc->parser == rc_rom_path) {
			if (prompt.complete == NULL) {
				prompt.complete =
					complete_path(arg, alen, NULL);
				prompt.skip = 0;
				rehash_prompt_complete_common();
			}
			if (prompt.complete != NULL) {
				char **ret = prompt.complete;

			rc_rom_path_retry:
				skip = prompt.skip;
				for (i = 0; (ret[i] != NULL); ++i) {
					if (skip == 0)
						break;
					--skip;
				}
				if (ret[i] == NULL) {
					if (prompt.skip != 0) {
						prompt.skip = 0;
						goto rc_rom_path_retry;
					}
				}
				else {
					prompt_common = prompt.common;
					s = backslashify
						((const uint8_t *)ret[i],
						 strlen(ret[i]), 0,
						 &prompt_common);
				}
			}
		}
		// Numbers.
		else if ((rc->parser == rc_number) ||
			 (rc->parser == rc_soundrate)) {
			char buf[10];

		rc_number_retry:
			if (snprintf(buf, sizeof(buf), "%d",
				     (int)prompt.skip) >= (int)sizeof(buf)) {
				prompt.skip = 0;
				goto rc_number_retry;
			}
			s = strdup(buf);
		}
		// CTV filters, scaling algorithms, Z80, M68K.
		else if ((names = ctv_names, rc->parser == rc_ctv) ||
			 (names = scaling_names, rc->parser == rc_scaling) ||
			 (names = emu_z80_names, rc->parser == rc_emu_z80) ||
			 (names = emu_m68k_names, rc->parser == rc_emu_m68k)) {
		rc_names_retry:
			skip = prompt.skip;
			for (i = 0; (names[i] != NULL); ++i) {
				if (skip == 0)
					break;
				--skip;
			}
			if (names[i] == NULL) {
				if (prompt.skip != 0) {
					prompt.skip = 0;
					goto rc_names_retry;
				}
			}
			else
				s = strdup(names[i]);
		}
		if (s == NULL)
			break;
		++prompt.skip;
		goto replace;
	}
	goto end;
replace:
	prompt_replace(p, pp.argo[pp.index].pos, pp.argo[pp.index].len,
		       (const uint8_t *)s, strlen(s));
	if (prompt_common) {
		unsigned int cursor;

		cursor = (pp.argo[pp.index].pos + prompt_common);
		if (cursor > p->history[(p->current)].length)
			cursor = p->history[(p->current)].length;
		if (cursor != p->cursor) {
			p->cursor = cursor;
			handle_prompt_complete_clear();
		}
	}
end:
	free(s);
	prompt_parse_clean(&pp);
	return 0;
}

static int handle_prompt(uint32_t ksym, uint16_t ksym_uni, md& megad)
{
	struct prompt::prompt_history *ph;
	size_t sz;
	uint8_t c[6];
	char *s;
	int ret = PROMPT_RET_CONT;
	struct prompt *p = &prompt.status;
	struct prompt_parse pp;

	if (ksym == 0)
		goto end;
	switch (ksym & ~KEYSYM_MOD_MASK) {
	case SDLK_UP:
		handle_prompt_complete_clear();
		prompt_older(p);
		break;
	case SDLK_DOWN:
		handle_prompt_complete_clear();
		prompt_newer(p);
		break;
	case SDLK_LEFT:
		handle_prompt_complete_clear();
		prompt_left(p);
		break;
	case SDLK_RIGHT:
		handle_prompt_complete_clear();
		prompt_right(p);
		break;
	case SDLK_HOME:
		handle_prompt_complete_clear();
		prompt_begin(p);
		break;
	case SDLK_END:
		handle_prompt_complete_clear();
		prompt_end(p);
		break;
	case SDLK_BACKSPACE:
		handle_prompt_complete_clear();
		prompt_backspace(p);
		break;
	case SDLK_DELETE:
		handle_prompt_complete_clear();
		prompt_delete(p);
		break;
	case SDLK_a:
		// ^A
		if ((ksym & KEYSYM_MOD_CTRL) == 0)
			goto other;
		handle_prompt_complete_clear();
		prompt_begin(p);
		break;
	case SDLK_e:
		// ^E
		if ((ksym & KEYSYM_MOD_CTRL) == 0)
			goto other;
		handle_prompt_complete_clear();
		prompt_end(p);
		break;
	case SDLK_u:
		// ^U
		if ((ksym & KEYSYM_MOD_CTRL) == 0)
			goto other;
		handle_prompt_complete_clear();
		prompt_clear(p);
		break;
	case SDLK_k:
		// ^K
		if ((ksym & KEYSYM_MOD_CTRL) == 0)
			goto other;
		handle_prompt_complete_clear();
		prompt_replace(p, p->cursor, ~0u, NULL, 0);
		break;
	case SDLK_w:
		// ^W
		if ((ksym & KEYSYM_MOD_CTRL) == 0)
			goto other;
		if (prompt_parse(p, &pp) == NULL)
			break;
		if (pp.argv[pp.index] == NULL) {
			if (pp.index == 0) {
				prompt_parse_clean(&pp);
				break;
			}
			--pp.index;
		}
		handle_prompt_complete_clear();
		if (pp.argv[(pp.index + 1)] != NULL)
			prompt_replace(p, pp.argo[pp.index].pos,
				       (pp.argo[(pp.index + 1)].pos -
					pp.argo[pp.index].pos),
				       NULL, 0);
		else
			prompt_replace(p, pp.argo[pp.index].pos, ~0u, NULL, 0);
		p->cursor = pp.argo[pp.index].pos;
		prompt_parse_clean(&pp);
		break;
	case SDLK_RETURN:
	case SDLK_KP_ENTER:
		handle_prompt_complete_clear();
		ret |= handle_prompt_enter(megad);
		break;
	case SDLK_ESCAPE:
		handle_prompt_complete_clear();
		ret |= PROMPT_RET_EXIT;
		break;
	case SDLK_TAB:
		if (ksym & KEYSYM_MOD_SHIFT)
			ret |= handle_prompt_complete(megad, true);
		else
			ret |= handle_prompt_complete(megad, false);
		break;
	default:
	other:
		if (ksym_uni == 0)
			break;
		handle_prompt_complete_clear();
		sz = utf32u8(c, ksym_uni);
		if ((sz != 0) &&
		    ((s = backslashify(c, sz, BACKSLASHIFY_NOQUOTES,
				       NULL)) != NULL)) {
			size_t i;

			for (i = 0; (i != strlen(s)); ++i)
				prompt_put(p, s[i]);
			free(s);
		}
		break;
	}
end:
	if ((ret & ~(PROMPT_RET_CONT | PROMPT_RET_ENTER)) == 0) {
		ph = &p->history[(p->current)];
		pd_message_cursor((p->cursor + 1),
				  "%s%.*s", prompt_str, ph->length, ph->line);
	}
	return ret;
}

// Controls enum. You must add new entries at the end. Do not change the order.
enum ctl_e {
	CTL_PAD1_UP,
	CTL_PAD1_DOWN,
	CTL_PAD1_LEFT,
	CTL_PAD1_RIGHT,
	CTL_PAD1_A,
	CTL_PAD1_B,
	CTL_PAD1_C,
	CTL_PAD1_X,
	CTL_PAD1_Y,
	CTL_PAD1_Z,
	CTL_PAD1_MODE,
	CTL_PAD1_START,
	CTL_PAD2_UP,
	CTL_PAD2_DOWN,
	CTL_PAD2_LEFT,
	CTL_PAD2_RIGHT,
	CTL_PAD2_A,
	CTL_PAD2_B,
	CTL_PAD2_C,
	CTL_PAD2_X,
	CTL_PAD2_Y,
	CTL_PAD2_Z,
	CTL_PAD2_MODE,
	CTL_PAD2_START,
#ifdef WITH_PICO
	CTL_PICO_PEN_UP,
	CTL_PICO_PEN_DOWN,
	CTL_PICO_PEN_LEFT,
	CTL_PICO_PEN_RIGHT,
	CTL_PICO_PEN_BUTTON,
#endif
	CTL_DGEN_QUIT,
	CTL_DGEN_CRAPTV_TOGGLE,
	CTL_DGEN_SCALING_TOGGLE,
	CTL_DGEN_RESET,
	CTL_DGEN_SLOT0,
	CTL_DGEN_SLOT1,
	CTL_DGEN_SLOT2,
	CTL_DGEN_SLOT3,
	CTL_DGEN_SLOT4,
	CTL_DGEN_SLOT5,
	CTL_DGEN_SLOT6,
	CTL_DGEN_SLOT7,
	CTL_DGEN_SLOT8,
	CTL_DGEN_SLOT9,
	CTL_DGEN_SLOT_NEXT,
	CTL_DGEN_SLOT_PREV,
	CTL_DGEN_SAVE,
	CTL_DGEN_LOAD,
	CTL_DGEN_Z80_TOGGLE,
	CTL_DGEN_CPU_TOGGLE,
	CTL_DGEN_STOP,
	CTL_DGEN_PROMPT,
	CTL_DGEN_GAME_GENIE,
	CTL_DGEN_VOLUME_INC,
	CTL_DGEN_VOLUME_DEC,
	CTL_DGEN_FULLSCREEN_TOGGLE,
	CTL_DGEN_FIX_CHECKSUM,
	CTL_DGEN_SCREENSHOT,
	CTL_DGEN_DEBUG_ENTER,
	CTL_
};

// Controls definitions.
struct ctl {
	const enum ctl_e type;
	intptr_t (*const rc)[RCB_NUM];
	int (*const press)(struct ctl&, md&);
	int (*const release)(struct ctl&, md&);
#define DEF 0, 0, 0, 0
	unsigned int pressed:1;
	unsigned int coord:1;
	unsigned int x:10;
	unsigned int y:10;
};

static int ctl_pad1(struct ctl& ctl, md& megad)
{
	switch (ctl.type) {
	case CTL_PAD1_UP:
		megad.pad[0] &= ~MD_UP_MASK;
		break;
	case CTL_PAD1_DOWN:
		megad.pad[0] &= ~MD_DOWN_MASK;
		break;
	case CTL_PAD1_LEFT:
		megad.pad[0] &= ~MD_LEFT_MASK;
		break;
	case CTL_PAD1_RIGHT:
		megad.pad[0] &= ~MD_RIGHT_MASK;
		break;
	case CTL_PAD1_A:
		megad.pad[0] &= ~MD_A_MASK;
		break;
	case CTL_PAD1_B:
		megad.pad[0] &= ~MD_B_MASK;
		break;
	case CTL_PAD1_C:
		megad.pad[0] &= ~MD_C_MASK;
		break;
	case CTL_PAD1_X:
		megad.pad[0] &= ~MD_X_MASK;
		break;
	case CTL_PAD1_Y:
		megad.pad[0] &= ~MD_Y_MASK;
		break;
	case CTL_PAD1_Z:
		megad.pad[0] &= ~MD_Z_MASK;
		break;
	case CTL_PAD1_MODE:
		megad.pad[0] &= ~MD_MODE_MASK;
		break;
	case CTL_PAD1_START:
		megad.pad[0] &= ~MD_START_MASK;
		break;
	default:
		break;
	}
	return 1;
}

static int ctl_pad1_release(struct ctl& ctl, md& megad)
{
	switch (ctl.type) {
	case CTL_PAD1_UP:
		megad.pad[0] |= MD_UP_MASK;
		break;
	case CTL_PAD1_DOWN:
		megad.pad[0] |= MD_DOWN_MASK;
		break;
	case CTL_PAD1_LEFT:
		megad.pad[0] |= MD_LEFT_MASK;
		break;
	case CTL_PAD1_RIGHT:
		megad.pad[0] |= MD_RIGHT_MASK;
		break;
	case CTL_PAD1_A:
		megad.pad[0] |= MD_A_MASK;
		break;
	case CTL_PAD1_B:
		megad.pad[0] |= MD_B_MASK;
		break;
	case CTL_PAD1_C:
		megad.pad[0] |= MD_C_MASK;
		break;
	case CTL_PAD1_X:
		megad.pad[0] |= MD_X_MASK;
		break;
	case CTL_PAD1_Y:
		megad.pad[0] |= MD_Y_MASK;
		break;
	case CTL_PAD1_Z:
		megad.pad[0] |= MD_Z_MASK;
		break;
	case CTL_PAD1_MODE:
		megad.pad[0] |= MD_MODE_MASK;
		break;
	case CTL_PAD1_START:
		megad.pad[0] |= MD_START_MASK;
		break;
	default:
		break;
	}
	return 1;
}

static int ctl_pad2(struct ctl& ctl, md& megad)
{
	switch (ctl.type) {
	case CTL_PAD2_UP:
		megad.pad[1] &= ~MD_UP_MASK;
		break;
	case CTL_PAD2_DOWN:
		megad.pad[1] &= ~MD_DOWN_MASK;
		break;
	case CTL_PAD2_LEFT:
		megad.pad[1] &= ~MD_LEFT_MASK;
		break;
	case CTL_PAD2_RIGHT:
		megad.pad[1] &= ~MD_RIGHT_MASK;
		break;
	case CTL_PAD2_A:
		megad.pad[1] &= ~MD_A_MASK;
		break;
	case CTL_PAD2_B:
		megad.pad[1] &= ~MD_B_MASK;
		break;
	case CTL_PAD2_C:
		megad.pad[1] &= ~MD_C_MASK;
		break;
	case CTL_PAD2_X:
		megad.pad[1] &= ~MD_X_MASK;
		break;
	case CTL_PAD2_Y:
		megad.pad[1] &= ~MD_Y_MASK;
		break;
	case CTL_PAD2_Z:
		megad.pad[1] &= ~MD_Z_MASK;
		break;
	case CTL_PAD2_MODE:
		megad.pad[1] &= ~MD_MODE_MASK;
		break;
	case CTL_PAD2_START:
		megad.pad[1] &= ~MD_START_MASK;
		break;
	default:
		break;
	}
	return 1;
}

static int ctl_pad2_release(struct ctl& ctl, md& megad)
{
	switch (ctl.type) {
	case CTL_PAD2_UP:
		megad.pad[1] |= MD_UP_MASK;
		break;
	case CTL_PAD2_DOWN:
		megad.pad[1] |= MD_DOWN_MASK;
		break;
	case CTL_PAD2_LEFT:
		megad.pad[1] |= MD_LEFT_MASK;
		break;
	case CTL_PAD2_RIGHT:
		megad.pad[1] |= MD_RIGHT_MASK;
		break;
	case CTL_PAD2_A:
		megad.pad[1] |= MD_A_MASK;
		break;
	case CTL_PAD2_B:
		megad.pad[1] |= MD_B_MASK;
		break;
	case CTL_PAD2_C:
		megad.pad[1] |= MD_C_MASK;
		break;
	case CTL_PAD2_X:
		megad.pad[1] |= MD_X_MASK;
		break;
	case CTL_PAD2_Y:
		megad.pad[1] |= MD_Y_MASK;
		break;
	case CTL_PAD2_Z:
		megad.pad[1] |= MD_Z_MASK;
		break;
	case CTL_PAD2_MODE:
		megad.pad[1] |= MD_MODE_MASK;
		break;
	case CTL_PAD2_START:
		megad.pad[1] |= MD_START_MASK;
		break;
	default:
		break;
	}
	return 1;
}

#ifdef WITH_PICO

static int ctl_pico_pen(struct ctl& ctl, md& megad)
{
	static unsigned int min_y = 0x1fc;
	static unsigned int max_y = 0x2f7;
	static unsigned int min_x = 0x3c;
	static unsigned int max_x = 0x17c;
	static const struct {
		enum ctl_e type;
		unsigned int coords:1;
		unsigned int dir:1;
		unsigned int lim[2];
	} motion[] = {
		{ CTL_PICO_PEN_UP, 1, 0, { min_y, max_y } },
		{ CTL_PICO_PEN_DOWN, 1, 1, { min_y, max_y } },
		{ CTL_PICO_PEN_LEFT, 0, 0, { min_x, max_x } },
		{ CTL_PICO_PEN_RIGHT, 0, 1, { min_x, max_x } }
	};
	unsigned int i;

	if (ctl.type == CTL_PICO_PEN_BUTTON) {
		megad.pad[0] &= ~MD_PICO_PENBTN_MASK;
		return 1;
	}
	// Use coordinates if available.
	if ((ctl.coord) &&
	    (screen.window_width != 0) &&
	    (screen.window_height != 0)) {
		megad.pico_pen_coords[1] =
			(min_y + ((ctl.y * (max_y - min_y)) /
				  screen.window_height));
		megad.pico_pen_coords[0] =
			(min_x + ((ctl.x * (max_x - min_x)) /
				  screen.window_width));
		return 1;
	}
	for (i = 0; (i != elemof(motion)); ++i) {
		unsigned int coords;

		if (motion[i].type != ctl.type)
			continue;
		coords = motion[i].coords;
		if (motion[i].dir)
			megad.pico_pen_coords[coords] += pico_pen_stride;
		else
			megad.pico_pen_coords[coords] -= pico_pen_stride;
		if ((megad.pico_pen_coords[coords] < motion[i].lim[0]) ||
		    (megad.pico_pen_coords[coords] > motion[i].lim[1]))
			megad.pico_pen_coords[coords] =
				motion[i].lim[motion[i].dir];
		break;
	}
	return 1;
}

static int ctl_pico_pen_release(struct ctl& ctl, md& megad)
{
	if (ctl.type == CTL_PICO_PEN_BUTTON)
		megad.pad[0] |= MD_PICO_PENBTN_MASK;
	return 1;
}

#endif

static int ctl_dgen_quit(struct ctl&, md&)
{
	return 0;
}

static int ctl_dgen_craptv_toggle(struct ctl&, md&)
{
#ifdef WITH_CTV
	dgen_craptv = ((dgen_craptv + 1) % NUM_CTV);
	filters_pluck_ctv();
	filters_insert(filters_find(ctv_names[dgen_craptv]));
	pd_message("Crap TV mode \"%s\".", ctv_names[dgen_craptv]);
#endif // WITH_CTV
	return 1;
}

static int ctl_dgen_scaling_toggle(struct ctl&, md&)
{
	dgen_scaling = ((dgen_scaling + 1) % NUM_SCALING);
	if (set_scaling(scaling_names[dgen_scaling]))
		pd_message("Scaling algorithm \"%s\" unavailable.",
			   scaling_names[dgen_scaling]);
	else
		pd_message("Using scaling algorithm \"%s\".",
			   scaling_names[dgen_scaling]);
	return 1;
}

static int ctl_dgen_reset(struct ctl&, md& megad)
{
	megad.reset();
	pd_message("Genesis reset.");
	return 1;
}

static int ctl_dgen_slot(struct ctl& ctl, md&)
{
	slot = ((int)ctl.type - CTL_DGEN_SLOT0);
	pd_message("Selected save slot %d.", slot);
	return 1;
}

static int ctl_dgen_slot_next(struct ctl&, md&)
{
	if (slot == 9)
		slot = 0;
	else
		slot++;
	pd_message("Selected next save slot (%d).", slot);
	return 1;
}

static int ctl_dgen_slot_prev(struct ctl&, md&)
{
	if (slot == 0)
		slot = 9;
	else
		slot--;
	pd_message("Selected previous save slot (%d).", slot);
	return 1;
}

static int ctl_dgen_save(struct ctl&, md& megad)
{
	md_save(megad);
	return 1;
}

static int ctl_dgen_load(struct ctl&, md& megad)
{
	md_load(megad);
	return 1;
}

// Cycle Z80 core.
static int ctl_dgen_z80_toggle(struct ctl&, md& megad)
{
	const char *msg;

	megad.cycle_z80();
	switch (megad.z80_core) {
#ifdef WITH_CZ80
	case md::Z80_CORE_CZ80:
		msg = "CZ80 core activated.";
		break;
#endif
#ifdef WITH_MZ80
	case md::Z80_CORE_MZ80:
		msg = "MZ80 core activated.";
		break;
#endif
#ifdef WITH_DRZ80
	case md::Z80_CORE_DRZ80:
		msg = "DrZ80 core activated.";
		break;
#endif
	default:
		msg = "Z80 core disabled.";
		break;
	}
	pd_message(msg);
	return 1;
}

// Added this CPU core hot swap.  Compile both Musashi and StarScream
// in, and swap on the fly like DirectX DGen. [PKH]
static int ctl_dgen_cpu_toggle(struct ctl&, md& megad)
{
	const char *msg;

	megad.cycle_cpu();
	switch (megad.cpu_emu) {
#ifdef WITH_STAR
	case md::CPU_EMU_STAR:
		msg = "StarScream CPU core activated.";
		break;
#endif
#ifdef WITH_MUSA
	case md::CPU_EMU_MUSA:
		msg = "Musashi CPU core activated.";
		break;
#endif
#ifdef WITH_CYCLONE
	case md::CPU_EMU_CYCLONE:
		msg = "Cyclone CPU core activated.";
		break;
#endif
	default:
		msg = "CPU core disabled.";
		break;
	}
	pd_message(msg);
	return 1;
}

static int ctl_dgen_stop(struct ctl&, md& megad)
{
	pd_message(stopped_str);
	if (stop_events(megad, STOPPED) != 0)
		return 0;
	return 1;
}

static int ctl_dgen_prompt(struct ctl&, md& megad)
{
	pd_message_cursor(strlen(prompt_str), prompt_str);
	if (stop_events(megad, PROMPT) != 0)
		return 0;
	return 1;
}

static int ctl_dgen_game_genie(struct ctl&, md& megad)
{
	pd_message_cursor(strlen(game_genie_str), game_genie_str);
	if (stop_events(megad, GAME_GENIE) != 0)
		return 0;
	return 1;
}

static int ctl_dgen_volume(struct ctl& ctl, md&)
{
	if (ctl.type == CTL_DGEN_VOLUME_INC)
		++dgen_volume;
	else
		--dgen_volume;
	if (dgen_volume < 0)
		dgen_volume = 0;
	else if (dgen_volume > 100)
		dgen_volume = 100;
	pd_message("Volume %d%%.", (int)dgen_volume);
	return 1;
}

static int ctl_dgen_fullscreen_toggle(struct ctl&, md&)
{
	switch (set_fullscreen(!screen.is_fullscreen)) {
	case -2:
		fprintf(stderr,
			"sdl: fatal error while trying to change screen"
			" resolution.\n");
		return 0;
	case -1:
		pd_message("Failed to toggle fullscreen mode.");
		break;
	default:
		pd_message("Fullscreen mode toggled.");
	}
	return 1;
}

static int ctl_dgen_fix_checksum(struct ctl&, md& megad)
{
	pd_message("Checksum fixed.");
	megad.fix_rom_checksum();
	return 1;
}

static int ctl_dgen_screenshot(struct ctl&, md& megad)
{
	do_screenshot(megad);
	return 1;
}

static int ctl_dgen_debug_enter(struct ctl&, md& megad)
{
#ifdef WITH_DEBUGGER
	stopped = 1;
	if (megad.debug_trap == false)
		megad.debug_enter();
	else
		megad.debug_leave();
#else
	(void)megad;
	pd_message("Debugger support not built in.");
#endif
	return 1;
}

static struct ctl control[] = {
	// Array indices and control[].type must match enum ctl_e's order.
	{ CTL_PAD1_UP, &pad1_up, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD1_DOWN, &pad1_down, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD1_LEFT, &pad1_left, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD1_RIGHT, &pad1_right, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD1_A, &pad1_a, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD1_B, &pad1_b, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD1_C, &pad1_c, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD1_X, &pad1_x, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD1_Y, &pad1_y, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD1_Z, &pad1_z, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD1_MODE, &pad1_mode, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD1_START, &pad1_start, ctl_pad1, ctl_pad1_release, DEF },
	{ CTL_PAD2_UP, &pad2_up, ctl_pad2, ctl_pad2_release, DEF },
	{ CTL_PAD2_DOWN, &pad2_down, ctl_pad2, ctl_pad2_release, DEF },
	{ CTL_PAD2_LEFT, &pad2_left, ctl_pad2, ctl_pad2_release, DEF },
	{ CTL_PAD2_RIGHT, &pad2_right, ctl_pad2, ctl_pad2_release, DEF },
	{ CTL_PAD2_A, &pad2_a, ctl_pad2, ctl_pad2_release, DEF },
	{ CTL_PAD2_B, &pad2_b, ctl_pad2, ctl_pad2_release, DEF },
	{ CTL_PAD2_C, &pad2_c, ctl_pad2, ctl_pad2_release, DEF },
	{ CTL_PAD2_X, &pad2_x, ctl_pad2, ctl_pad2_release, DEF },
	{ CTL_PAD2_Y, &pad2_y, ctl_pad2, ctl_pad2_release, DEF },
	{ CTL_PAD2_Z, &pad2_z, ctl_pad2, ctl_pad2_release, DEF },
	{ CTL_PAD2_MODE, &pad2_mode, ctl_pad2, ctl_pad2_release, DEF },
	{ CTL_PAD2_START, &pad2_start, ctl_pad2, ctl_pad2_release, DEF },
#ifdef WITH_PICO
	{ CTL_PICO_PEN_UP,
	  &pico_pen_up, ctl_pico_pen, ctl_pico_pen_release, DEF },
	{ CTL_PICO_PEN_DOWN,
	  &pico_pen_down, ctl_pico_pen, ctl_pico_pen_release, DEF },
	{ CTL_PICO_PEN_LEFT,
	  &pico_pen_left, ctl_pico_pen, ctl_pico_pen_release, DEF },
	{ CTL_PICO_PEN_RIGHT,
	  &pico_pen_right, ctl_pico_pen, ctl_pico_pen_release, DEF },
	{ CTL_PICO_PEN_BUTTON,
	  &pico_pen_button, ctl_pico_pen, ctl_pico_pen_release, DEF },
#endif
	{ CTL_DGEN_QUIT, &dgen_quit, ctl_dgen_quit, NULL, DEF },
	{ CTL_DGEN_CRAPTV_TOGGLE,
	  &dgen_craptv_toggle, ctl_dgen_craptv_toggle, NULL, DEF },
	{ CTL_DGEN_SCALING_TOGGLE,
	  &dgen_scaling_toggle, ctl_dgen_scaling_toggle, NULL, DEF },
	{ CTL_DGEN_RESET, &dgen_reset, ctl_dgen_reset, NULL, DEF },
	{ CTL_DGEN_SLOT0, &dgen_slot_0, ctl_dgen_slot, NULL, DEF },
	{ CTL_DGEN_SLOT1, &dgen_slot_1, ctl_dgen_slot, NULL, DEF },
	{ CTL_DGEN_SLOT2, &dgen_slot_2, ctl_dgen_slot, NULL, DEF },
	{ CTL_DGEN_SLOT3, &dgen_slot_3, ctl_dgen_slot, NULL, DEF },
	{ CTL_DGEN_SLOT4, &dgen_slot_4, ctl_dgen_slot, NULL, DEF },
	{ CTL_DGEN_SLOT5, &dgen_slot_5, ctl_dgen_slot, NULL, DEF },
	{ CTL_DGEN_SLOT6, &dgen_slot_6, ctl_dgen_slot, NULL, DEF },
	{ CTL_DGEN_SLOT7, &dgen_slot_7, ctl_dgen_slot, NULL, DEF },
	{ CTL_DGEN_SLOT8, &dgen_slot_8, ctl_dgen_slot, NULL, DEF },
	{ CTL_DGEN_SLOT9, &dgen_slot_9, ctl_dgen_slot, NULL, DEF },
	{ CTL_DGEN_SLOT_NEXT, &dgen_slot_next, ctl_dgen_slot_next, NULL, DEF },
	{ CTL_DGEN_SLOT_PREV, &dgen_slot_prev, ctl_dgen_slot_prev, NULL, DEF },
	{ CTL_DGEN_SAVE, &dgen_save, ctl_dgen_save, NULL, DEF },
	{ CTL_DGEN_LOAD, &dgen_load, ctl_dgen_load, NULL, DEF },
	{ CTL_DGEN_Z80_TOGGLE,
	  &dgen_z80_toggle, ctl_dgen_z80_toggle, NULL, DEF },
	{ CTL_DGEN_CPU_TOGGLE,
	  &dgen_cpu_toggle, ctl_dgen_cpu_toggle, NULL, DEF },
	{ CTL_DGEN_STOP, &dgen_stop, ctl_dgen_stop, NULL, DEF },
	{ CTL_DGEN_PROMPT, &dgen_prompt, ctl_dgen_prompt, NULL, DEF },
	{ CTL_DGEN_GAME_GENIE,
	  &dgen_game_genie, ctl_dgen_game_genie, NULL, DEF },
	{ CTL_DGEN_VOLUME_INC,
	  &dgen_volume_inc, ctl_dgen_volume, NULL, DEF },
	{ CTL_DGEN_VOLUME_DEC,
	  &dgen_volume_dec, ctl_dgen_volume, NULL, DEF },
	{ CTL_DGEN_FULLSCREEN_TOGGLE,
	  &dgen_fullscreen_toggle, ctl_dgen_fullscreen_toggle, NULL, DEF },
	{ CTL_DGEN_FIX_CHECKSUM,
	  &dgen_fix_checksum, ctl_dgen_fix_checksum, NULL, DEF },
	{ CTL_DGEN_SCREENSHOT,
	  &dgen_screenshot, ctl_dgen_screenshot, NULL, DEF },
	{ CTL_DGEN_DEBUG_ENTER,
	  &dgen_debug_enter, ctl_dgen_debug_enter, NULL, DEF },
	{ CTL_, NULL, NULL, NULL, DEF }
};

static struct {
	char const* name; ///< Controller button name.
	enum ctl_e const id[2]; ///< Controls indices in control[].
	bool once; ///< If button has been pressed once.
	bool twice; ///< If button has been pressed twice.
	enum rc_binding_type type; ///< Type of code.
	intptr_t code; ///< Temporary code.
} calibration_steps[] = {
	{ "START", { CTL_PAD1_START, CTL_PAD2_START },
	  false, false, RCB_NUM, -1 },
	{ "MODE", { CTL_PAD1_MODE, CTL_PAD2_MODE },
	  false, false, RCB_NUM, -1 },
	{ "A", { CTL_PAD1_A, CTL_PAD2_A },
	  false, false, RCB_NUM, -1 },
	{ "B", { CTL_PAD1_B, CTL_PAD2_B },
	  false, false, RCB_NUM, -1 },
	{ "C", { CTL_PAD1_C, CTL_PAD2_C },
	  false, false, RCB_NUM, -1 },
	{ "X", { CTL_PAD1_X, CTL_PAD2_X },
	  false, false, RCB_NUM, -1 },
	{ "Y", { CTL_PAD1_Y, CTL_PAD2_Y },
	  false, false, RCB_NUM, -1 },
	{ "Z", { CTL_PAD1_Z, CTL_PAD2_Z },
	  false, false, RCB_NUM, -1 },
	{ "UP", { CTL_PAD1_UP, CTL_PAD2_UP },
	  false, false, RCB_NUM, -1 },
	{ "DOWN", { CTL_PAD1_DOWN, CTL_PAD2_DOWN },
	  false, false, RCB_NUM, -1 },
	{ "LEFT", { CTL_PAD1_LEFT, CTL_PAD2_LEFT },
	  false, false, RCB_NUM, -1 },
	{ "RIGHT", { CTL_PAD1_RIGHT, CTL_PAD2_RIGHT },
	  false, false, RCB_NUM, -1 },
	{ NULL, { CTL_, CTL_ },
	  false, false, RCB_NUM, -1 }
};

/**
 * Handle input during calibration process.
 * @param type Type of code.
 * @param code Code to process.
 */
static void manage_calibration(enum rc_binding_type type, intptr_t code)
{
	unsigned int step = 0;

	assert(calibrating_controller < 2);
	if (!calibrating) {
		// Stop emulation, enter calibration mode.
		freeze(true);
		calibrating = true;
		filter_text_str[0] = '\0';
		filter_text_msg(FILTER_TEXT_BG_BLACK
				FILTER_TEXT_CENTER
				FILTER_TEXT_8X13
				"CONTROLLER %u CALIBRATION\n"
				"\n"
				FILTER_TEXT_7X6
				FILTER_TEXT_LEFT
				"Press each button twice,\n"
				"or two different buttons to skip them.\n"
				"\n",
				(calibrating_controller + 1));
		filters_pluck(&filter_text_def);
		filters_insert(&filter_text_def);
		goto ask;
	}
	while (step != elemof(calibration_steps))
		if ((calibration_steps[step].once == true) &&
		    (calibration_steps[step].twice == true))
			++step;
		else
			break;
	if (step == elemof(calibration_steps)) {
		// Reset everything.
		for (step = 0; (step != elemof(calibration_steps)); ++step) {
			calibration_steps[step].once = false;
			calibration_steps[step].twice = false;
			calibration_steps[step].type = RCB_NUM;
			calibration_steps[step].code = -1;
		}
		// Restart emulation.
		freeze(false);
		calibrating = false;
		filters_pluck(&filter_text_def);
		return;
	}
	if (calibration_steps[step].once == false) {
		char *dump;

		if (type == RCBK)
			dump = dump_keysym(code);
		else if (type == RCBJ)
			dump = dump_joypad(code);
		else if (type == RCBM)
			dump = dump_mouse(code);
		else
			dump = NULL;
		assert(calibration_steps[step].twice == false);
		calibration_steps[step].once = true;
		calibration_steps[step].type = type;
		calibration_steps[step].code = code;
		filter_text_msg("\"%s\", confirm: ", (dump ? dump : ""));
		free(dump);
	}
	else if (calibration_steps[step].twice == false) {
		calibration_steps[step].twice = true;
		if ((calibration_steps[step].type == type) &&
		    (calibration_steps[step].code == code))
			filter_text_msg("OK\n");
		else {
			calibration_steps[step].type = RCB_NUM;
			calibration_steps[step].code = -1;
			filter_text_msg("none\n");
		}
	}
	if ((calibration_steps[step].once != true) ||
	    (calibration_steps[step].twice != true))
		return;
	++step;
ask:
	if (step == elemof(calibration_steps)) {
		code = calibration_steps[(elemof(calibration_steps) - 1)].code;
		if (code == -1)
			filter_text_msg("\n"
					"Aborted.");
		else {
			unsigned int i;

			for (i = 0; (i != elemof(calibration_steps)); ++i) {
				enum ctl_e id;

				id = calibration_steps[i].id
					[calibrating_controller];
				type = calibration_steps[i].type;
				code = calibration_steps[i].code;
				assert((size_t)id < elemof(control));
				assert(control[id].type == id);
				if ((id != CTL_) && (type != RCB_NUM))
					(*control[id].rc)[type] = code;
			}
			filter_text_msg("\n"
					"Applied.");
		}
	}
	else if (calibration_steps[step].name != NULL)
		filter_text_msg("%s: ", calibration_steps[step].name);
	else
		filter_text_msg("\n"
				"Press any button twice to apply settings:\n"
				"");
}

static struct rc_binding_item combos[64];

static void manage_combos(md& md, bool pressed, enum rc_binding_type type,
			  intptr_t code)
{
	unsigned int i;

	(void)md;
	for (i = 0; (i != elemof(combos)); ++i) {
		if (!combos[i].assigned) {
			if (!pressed)
				return; // Not in the list, nothing to do.
			// Not found, add it to the list.
			combos[i].assigned = true;
			combos[i].type = type;
			combos[i].code = code;
			return;
		}
		if ((combos[i].type != type) || (combos[i].code != code))
			continue; // Does not match.
		if (pressed)
			return; // Already pressed.
		// Release entry.
		memmove(&combos[i], &combos[i + 1],
			((elemof(combos) - (i + 1)) * sizeof(combos[i])));
		break;
	}
}

static bool check_combos(md& md, struct rc_binding_item item[],
			 unsigned int num)
{
	unsigned int i;
	unsigned int found = 0;

	(void)md;
	for (i = 0; (i != num); ++i) {
		unsigned int j;

		if (!item[i].assigned) {
			num = i;
			break;
		}
		for (j = 0; (j != elemof(combos)); ++j) {
			if (!combos[j].assigned)
				break;
			if ((combos[j].type != item[i].type) ||
			    (combos[j].code != item[i].code))
				continue;
			++found;
			break;
		}
	}
	if (num == 0)
		return false;
	return (found == num);
}

static int manage_bindings(md& md, bool pressed, enum rc_binding_type type,
			   intptr_t code)
{
	struct rc_binding *rcb = rc_binding_head.next;
	size_t pos = 0;
	size_t seek = 0;

	if ((dgen_buttons) && (pressed)) {
		char *dump;

		if (type == RCBK)
			dump = dump_keysym(code);
		else if (type == RCBJ)
			dump = dump_joypad(code);
		else if (type == RCBM)
			dump = dump_mouse(code);
		else
			dump = NULL;
		if (dump != NULL) {
			pd_message("Pressed \"%s\".", dump);
			free(dump);
		}
	}
	while (rcb != &rc_binding_head) {
		if ((pos < seek) ||
		    (!check_combos(md, rcb->item, elemof(rcb->item)))) {
			++pos;
			rcb = rcb->next;
			continue;
		}
		assert(rcb->to != NULL);
		assert((intptr_t)rcb->to != -1);
		// For keyboard and joystick bindings, perform related action.
		if ((type = RCBK, !strncasecmp("key_", rcb->to, 4)) ||
		    (type = RCBJ, !strncasecmp("joy_", rcb->to, 4)) ||
		    (type = RCBM, !strncasecmp("mou_", rcb->to, 4))) {
			struct rc_field *rcf = rc_fields;

			while (rcf->fieldname != NULL) {
				struct ctl *ctl = control;

				if (strcasecmp(rcb->to, rcf->fieldname)) {
					++rcf;
					continue;
				}
				while (ctl->rc != NULL) {
					if (&(*ctl->rc)[type] !=
					    rcf->variable) {
						++ctl;
						continue;
					}
					// Got it, finally.
					if (pressed) {
						assert(ctl->press != NULL);
						if (!ctl->press(*ctl, md))
							return 0;
					}
					else if (ctl->release != NULL) {
						if (!ctl->release(*ctl, md))
							return 0;
					}
					break;
				}
				break;
			}
		}
		// Otherwise, pass it to the prompt.
		else if (pressed) {
			handle_prompt_complete_clear();
			prompt_replace(&prompt.status, 0, 0,
				       (uint8_t *)rcb->to, strlen(rcb->to));
			if (handle_prompt_enter(md) & PROMPT_RET_ERROR)
				return 0;
		}
		// In case the current (or any other binding) has been
		// removed, rewind and seek to the next position.
		rcb = rc_binding_head.next;
		seek = (pos + 1);
		pos = 0;
	}
	return 1;
}

static int manage_game_genie(md& megad, intptr_t ksym, intptr_t ksym_uni)
{
	static char buf[12];
	static kb_input_t input = { buf, 0, sizeof(buf) };
	unsigned int len = strlen(game_genie_str);

	switch (kb_input(&input, ksym, ksym_uni)) {
		unsigned int errors;
		unsigned int applied;
		unsigned int reverted;

	case KB_INPUT_ENTERED:
		megad.patch(input.buf, &errors, &applied, &reverted);
		if (errors)
			pd_message("Invalid code.");
		else if (reverted)
			pd_message("Reverted.");
		else if (applied)
			pd_message("Applied.");
		else {
		case KB_INPUT_ABORTED:
			pd_message("Aborted.");
		}
		goto over;
	case KB_INPUT_CONSUMED:
		pd_message_cursor((len + input.pos), "%s%.*s", game_genie_str,
				  (int)input.pos, buf);
		break;
	case KB_INPUT_IGNORED:
		break;
	}
	return 0;
over:
	input.buf = buf;
	input.pos = 0;
	input.size = sizeof(buf);
	memset(buf, 0, sizeof(buf));
	return 1;
}

#ifdef WITH_PICO

static void manage_pico_pen(md& megad)
{
	static unsigned long pico_pen_last_update;
	unsigned long pico_pen_now;

	if (!megad.pico_enabled)
		return;
	// Repeat pen motion as long as buttons are not released.
	// This is not necessary when pen is managed by direct coordinates.
	if ((((control[CTL_PICO_PEN_UP].pressed) &&
	      (!control[CTL_PICO_PEN_UP].coord)) ||
	     ((control[CTL_PICO_PEN_DOWN].pressed) &&
	      (!control[CTL_PICO_PEN_DOWN].coord)) ||
	     ((control[CTL_PICO_PEN_LEFT].pressed) &&
	      (!control[CTL_PICO_PEN_LEFT].coord)) ||
	     ((control[CTL_PICO_PEN_RIGHT].pressed) &&
	      (!control[CTL_PICO_PEN_RIGHT].coord))) &&
	    (pico_pen_now = pd_usecs(),
	     ((pico_pen_now - pico_pen_last_update) >=
	      ((unsigned long)pico_pen_delay * 1000)))) {
		if (control[CTL_PICO_PEN_UP].pressed)
			ctl_pico_pen
				(control[CTL_PICO_PEN_UP], megad);
		if (control[CTL_PICO_PEN_DOWN].pressed)
			ctl_pico_pen
				(control[CTL_PICO_PEN_DOWN], megad);
		if (control[CTL_PICO_PEN_LEFT].pressed)
			ctl_pico_pen
				(control[CTL_PICO_PEN_LEFT], megad);
		if (control[CTL_PICO_PEN_RIGHT].pressed)
			ctl_pico_pen
				(control[CTL_PICO_PEN_RIGHT], megad);
		pico_pen_last_update = pico_pen_now;
	}
}

#endif

static bool mouse_is_grabbed()
{
	return (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON);
}

static void mouse_grab(bool grab)
{
	SDL_GrabMode mode = SDL_WM_GrabInput(SDL_GRAB_QUERY);

	if ((grab) && (!pd_freeze) && (mode == SDL_GRAB_OFF)) {
		// Hide the cursor.
		SDL_ShowCursor(0);
		pd_message("Mouse trapped. Stop emulation to release.");
		SDL_WM_GrabInput(SDL_GRAB_ON);
	}
	else if ((!grab) && (mode == SDL_GRAB_ON)) {
		SDL_ShowCursor(1);
		SDL_WM_GrabInput(SDL_GRAB_OFF);
	}
}

static int stop_events(md& megad, enum events status)
{
	struct ctl* ctl;

	stopped = 1;
	freeze(true);
	events = status;
	// Release controls.
	for (ctl = control; (ctl->rc != NULL); ++ctl) {
		if (ctl->pressed == false)
			continue;
		ctl->pressed = false;
		ctl->coord = false;
		if ((ctl->release != NULL) &&
		    (ctl->release(*ctl, megad) == 0))
			return -1; // XXX do something about this.
	}
	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,
			    SDL_DEFAULT_REPEAT_INTERVAL);
	// Switch out of fullscreen mode (assuming this is supported)
	if (screen.is_fullscreen) {
		if (set_fullscreen(0) < -1)
			return -1;
		pd_graphics_update(true);
	}
	mouse_grab(false);
	return 0;
}

static void restart_events(md& megad)
{
	(void)megad;
	stopped = 1;
	freeze(false);
	handle_prompt_complete_clear();
	SDL_EnableKeyRepeat(0, 0);
	events = STARTED;
}

static struct {
	unsigned long when[0x100];
	uint8_t enabled[0x100 / 8];
	unsigned int count;
} mouse_motion_release;

#define MOUSE_MOTION_RELEASE_IS_ENABLED(which) \
	(mouse_motion_release.enabled[(which) / 8] & (1 << ((which) % 8)))
#define MOUSE_MOTION_RELEASE_DISABLE(which) \
	(mouse_motion_release.enabled[(which) / 8] &= ~(1 << ((which) % 8)))
#define MOUSE_MOTION_RELEASE_ENABLE(which) \
	(mouse_motion_release.enabled[(which) / 8] |= (1 << ((which) % 8)))

static void mouse_motion_delay_release(unsigned int which, bool enable)
{
	if (which >= elemof(mouse_motion_release.when)) {
		DEBUG(("mouse index too high (%u)", which));
		return;
	}
	if (!enable) {
		if (!MOUSE_MOTION_RELEASE_IS_ENABLED(which))
			return;
		MOUSE_MOTION_RELEASE_DISABLE(which);
		assert(mouse_motion_release.count != 0);
		--mouse_motion_release.count;
		return;
	}
	if (!MOUSE_MOTION_RELEASE_IS_ENABLED(which)) {
		MOUSE_MOTION_RELEASE_ENABLE(which);
		++mouse_motion_release.count;
		assert(mouse_motion_release.count <=
		       elemof(mouse_motion_release.when));
	}
	mouse_motion_release.when[which] =
		(pd_usecs() + (dgen_mouse_delay * 1000));
}

static bool mouse_motion_released(SDL_Event *event)
{
	unsigned int i;
	unsigned long now;

	if (mouse_motion_release.count == 0)
		return false;
	now = pd_usecs();
	for (i = 0; (i != mouse_motion_release.count); ++i) {
		unsigned long diff;

		if (!MOUSE_MOTION_RELEASE_IS_ENABLED(i))
			continue;
		diff = (mouse_motion_release.when[i] - now);
		if (diff < (unsigned long)(dgen_mouse_delay * 1000))
			continue;
		event->motion.type = SDL_MOUSEMOTION;
		event->motion.which = i;
		event->motion.xrel = 0;
		event->motion.yrel = 0;
		MOUSE_MOTION_RELEASE_DISABLE(i);
		--mouse_motion_release.count;
		return true;
	}
	return false;
}

#define MOUSE_SHOW_USECS (unsigned long)(2 * 1000000)

// The massive event handler!
// I know this is an ugly beast, but please don't be discouraged. If you need
// help, don't be afraid to ask me how something works. Basically, just handle
// all the event keys, or even ignore a few if they don't make sense for your
// interface.
int pd_handle_events(md &megad)
{
	static uint16_t kpress[0x100];
#ifdef WITH_DEBUGGER
	static bool debug_trap;
#endif
	static unsigned long hide_mouse_when;
	static bool hide_mouse;
#ifdef WITH_JOYSTICK
	static uint32_t const axis_value[][3] = {
		// { pressed, [implicitly released ...] }
		{ JS_AXIS_NEGATIVE, JS_AXIS_BETWEEN, JS_AXIS_POSITIVE },
		{ JS_AXIS_POSITIVE, JS_AXIS_BETWEEN, JS_AXIS_NEGATIVE },
		{ JS_AXIS_BETWEEN, JS_AXIS_POSITIVE, JS_AXIS_NEGATIVE }
	};
	static uint32_t const hat_value[][2] = {
		// { SDL value, pressed }
		{ SDL_HAT_UP, JS_HAT_UP },
		{ SDL_HAT_RIGHT, JS_HAT_RIGHT },
		{ SDL_HAT_DOWN, JS_HAT_DOWN },
		{ SDL_HAT_LEFT, JS_HAT_LEFT }
	};
	unsigned int hat_value_map;
	intptr_t joypad;
	bool pressed;
#endif
	uint32_t plist[8];
	uint32_t rlist[8];
	unsigned int i, pi, ri;
	SDL_Event event;
	uint16_t ksym_uni;
	intptr_t ksym;
	intptr_t mouse;
	unsigned int which;

#ifdef WITH_DEBUGGER
	if ((megad.debug_trap) && (megad.debug_enter() < 0))
		return 0;
	if (debug_trap != megad.debug_trap) {
		debug_trap = megad.debug_trap;
		if (debug_trap)
			mouse_grab(false);
		if (sound.cbuf.size)
			SDL_PauseAudio(debug_trap == true);
	}
#endif
	if ((hide_mouse) &&
	    ((hide_mouse_when - pd_usecs()) >= MOUSE_SHOW_USECS)) {
		if (!mouse_is_grabbed())
			SDL_ShowCursor(0);
		hide_mouse = false;
	}
next_event:
	if (mouse_motion_released(&event))
		goto mouse_motion;
	if (!SDL_PollEvent(&event)) {
#ifdef WITH_PICO
		manage_pico_pen(megad);
#endif
		return 1;
	}
	switch (event.type) {
#ifdef WITH_JOYSTICK
	case SDL_JOYAXISMOTION:
		if (event.jaxis.value <= -16384)
			i = 0;
		else if (event.jaxis.value >= 16384)
			i = 1;
		else
			i = 2;
		plist[0] = JS_AXIS(event.jaxis.which,
				   event.jaxis.axis,
				   axis_value[i][0]);
		rlist[0] = JS_AXIS(event.jaxis.which,
				   event.jaxis.axis,
				   axis_value[i][1]);
		rlist[1] = JS_AXIS(event.jaxis.which,
				   event.jaxis.axis,
				   axis_value[i][2]);
		// "between" causes problems during calibration, ignore it.
		if (axis_value[i][0] == JS_AXIS_BETWEEN)
			pi = 0;
		else
			pi = 1;
		ri = 2;
		goto joypad_axis;
	case SDL_JOYHATMOTION:
		pi = 0;
		ri = 0;
		hat_value_map = 0;
		for (i = 0; (i != elemof(hat_value)); ++i)
			if (event.jhat.value & hat_value[i][0]) {
				plist[pi++] = JS_HAT(event.jhat.which,
						     event.jhat.hat,
						     hat_value[i][1]);
				hat_value_map |= (1 << i);
			}
		for (i = 0; (i != elemof(hat_value)); ++i)
			if ((hat_value_map & (1 << i)) == 0)
				rlist[ri++] = JS_HAT(event.jhat.which,
						     event.jhat.hat,
						     hat_value[i][1]);
	joypad_axis:
		for (i = 0; (i != ri); ++i)
			manage_combos(megad, false, RCBJ, rlist[i]);
		for (i = 0; (i != pi); ++i)
			manage_combos(megad, true, RCBJ, plist[i]);
		if (events != STARTED)
			break;
		if (calibrating) {
			for (i = 0; ((calibrating) && (i != pi)); ++i)
				manage_calibration(RCBJ, plist[i]);
			break;
		}
		for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) {
			// Release buttons first.
			for (i = 0; (i != ri); ++i) {
				if ((ctl->pressed == false) ||
				    ((uint32_t)(*ctl->rc)[RCBJ] != rlist[i]))
					continue;
				ctl->pressed = false;
				ctl->coord = false;
				if ((ctl->release != NULL) &&
				    (ctl->release(*ctl, megad) == 0))
					return 0;
			}
			for (i = 0; (i != pi); ++i) {
				if ((uint32_t)(*ctl->rc)[RCBJ] == plist[i]) {
					assert(ctl->press != NULL);
					ctl->pressed = true;
					ctl->coord = false;
					if (ctl->press(*ctl, megad) == 0)
						return 0;
				}
			}
		}
		for (i = 0; (i != ri); ++i)
			if (!manage_bindings(megad, false, RCBJ, rlist[i]))
				return 0;
		for (i = 0; (i != pi); ++i)
			if (!manage_bindings(megad, true, RCBJ, plist[i]))
				return 0;
		break;
	case SDL_JOYBUTTONDOWN:
		assert(event.jbutton.state == SDL_PRESSED);
		pressed = true;
		goto joypad_button;
	case SDL_JOYBUTTONUP:
		assert(event.jbutton.state == SDL_RELEASED);
		pressed = false;
	joypad_button:
		joypad = JS_BUTTON(event.jbutton.which, event.jbutton.button);
		manage_combos(megad, pressed, RCBJ, joypad);
		if (events != STARTED)
			break;
		if (calibrating) {
			if (pressed)
				manage_calibration(RCBJ, joypad);
			break;
		}
		for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) {
			if ((*ctl->rc)[RCBJ] != joypad)
				continue;
			ctl->pressed = pressed;
			ctl->coord = false;
			if (pressed == false) {
				if ((ctl->release != NULL) &&
				    (ctl->release(*ctl, megad) == 0))
					return 0;
			}
			else {
				assert(ctl->press != NULL);
				if (ctl->press(*ctl, megad) == 0)
					return 0;
			}
		}
		if (manage_bindings(megad, pressed, RCBJ, joypad) == 0)
			return 0;
		break;
#endif // WITH_JOYSTICK
	case SDL_KEYDOWN:
		ksym = event.key.keysym.sym;
		ksym_uni = event.key.keysym.unicode;
		if ((ksym_uni < 0x20) ||
		    ((ksym >= SDLK_KP0) && (ksym <= SDLK_KP_EQUALS)))
			ksym_uni = 0;
		kpress[(ksym & 0xff)] = ksym_uni;
		if (ksym_uni)
			ksym = ksym_uni;
		else if (event.key.keysym.mod & KMOD_SHIFT)
			ksym |= KEYSYM_MOD_SHIFT;

		// Check for modifiers
		if (event.key.keysym.mod & KMOD_CTRL)
			ksym |= KEYSYM_MOD_CTRL;
		if (event.key.keysym.mod & KMOD_ALT)
			ksym |= KEYSYM_MOD_ALT;
		if (event.key.keysym.mod & KMOD_META)
			ksym |= KEYSYM_MOD_META;

		manage_combos(megad, true, RCBK, ksym);

		if (calibrating) {
			manage_calibration(RCBK, ksym);
			break;
		}

		switch (events) {
			int ret;

		case STARTED:
			break;
		case PROMPT:
		case STOPPED_PROMPT:
			ret = handle_prompt(ksym, ksym_uni, megad);
			if (ret & PROMPT_RET_ERROR) {
				restart_events(megad);
				return 0;
			}
			if (ret & PROMPT_RET_EXIT) {
				if (events == STOPPED_PROMPT) {
					// Return to stopped mode.
					pd_message(stopped_str);
					events = STOPPED;
					goto next_event;
				}
				if ((ret & PROMPT_RET_MSG) == 0)
					pd_message("RUNNING.");
				restart_events(megad);
				goto next_event;
			}
			if (ret & PROMPT_RET_ENTER) {
				// Back to the prompt only in stopped mode.
				if (events == STOPPED_PROMPT)
					goto next_event;
				if ((ret & PROMPT_RET_MSG) == 0)
					pd_message("");
				restart_events(megad);
				goto next_event;
			}
			// PROMPT_RET_CONT
			goto next_event;
		case GAME_GENIE:
		case STOPPED_GAME_GENIE:
			if (manage_game_genie(megad, ksym, ksym_uni) == 0)
				goto next_event;
			if (events == STOPPED_GAME_GENIE) {
				// Return to stopped mode.
				pd_message(stopped_str);
				events = STOPPED;
			}
			else
				restart_events(megad);
			goto next_event;
		case STOPPED:
			// In basic stopped mode, handle a few keysyms.
			if (ksym == dgen_game_genie[0]) {
				pd_message_cursor(strlen(game_genie_str),
						  game_genie_str);
				events = STOPPED_GAME_GENIE;
			}
			else if (ksym == dgen_prompt[0]) {
				pd_message_cursor(strlen(prompt_str),
						  prompt_str);
				events = STOPPED_PROMPT;
			}
			else if (ksym == dgen_quit[0]) {
				restart_events(megad);
				return 0;
			}
			else if (ksym == dgen_stop[0]) {
				pd_message("RUNNING.");
				restart_events(megad);
			}
#ifdef WITH_DEBUGGER
			else if (ksym == dgen_debug_enter[0])
				ctl_dgen_debug_enter(*(struct ctl *)0, megad);
#endif
		default:
			goto next_event;
		}

		for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) {
			if (ksym != (*ctl->rc)[RCBK])
				continue;
			assert(ctl->press != NULL);
			ctl->pressed = true;
			ctl->coord = false;
			if (ctl->press(*ctl, megad) == 0)
				return 0;
		}
		if (manage_bindings(megad, true, RCBK, ksym) == 0)
			return 0;
		break;
	case SDL_KEYUP:
		ksym = event.key.keysym.sym;
		ksym_uni = kpress[(ksym & 0xff)];
		if ((ksym_uni < 0x20) ||
		    ((ksym >= SDLK_KP0) && (ksym <= SDLK_KP_EQUALS)))
			ksym_uni = 0;
		kpress[(ksym & 0xff)] = 0;
		if (ksym_uni)
			ksym = ksym_uni;

		manage_combos(megad, false, RCBK, ksym);
		manage_combos(megad, false, RCBK, (ksym | KEYSYM_MOD_ALT));
		manage_combos(megad, false, RCBK, (ksym | KEYSYM_MOD_SHIFT));
		manage_combos(megad, false, RCBK, (ksym | KEYSYM_MOD_CTRL));
		manage_combos(megad, false, RCBK, (ksym | KEYSYM_MOD_META));

		if (calibrating)
			break;
		if (events != STARTED)
			break;

		// The only time we care about key releases is for the
		// controls, but ignore key modifiers so they never get stuck.
		for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) {
			if (ksym != ((*ctl->rc)[RCBK] & ~KEYSYM_MOD_MASK))
				continue;
			ctl->pressed = false;
			ctl->coord = false;
			if ((ctl->release != NULL) &&
			    (ctl->release(*ctl, megad) == 0))
				return 0;
		}
		if (manage_bindings(megad, false, RCBK, ksym) == 0)
			return 0;
		break;
	case SDL_MOUSEMOTION:
		if (!mouse_is_grabbed()) {
			// Only show mouse pointer for a few seconds.
			SDL_ShowCursor(1);
			hide_mouse_when = (pd_usecs() + MOUSE_SHOW_USECS);
			hide_mouse = true;
			break;
		}
	mouse_motion:
		which = event.motion.which;
		pi = 0;
		ri = 0;
		if (event.motion.xrel < 0) {
			plist[pi++] = MO_MOTION(which, 'l');
			rlist[ri++] = MO_MOTION(which, 'r');
		}
		else if (event.motion.xrel > 0) {
			plist[pi++] = MO_MOTION(which, 'r');
			rlist[ri++] = MO_MOTION(which, 'l');
		}
		else {
			rlist[ri++] = MO_MOTION(which, 'r');
			rlist[ri++] = MO_MOTION(which, 'l');
		}
		if (event.motion.yrel < 0) {
			plist[pi++] = MO_MOTION(which, 'u');
			rlist[ri++] = MO_MOTION(which, 'd');
		}
		else if (event.motion.yrel > 0) {
			plist[pi++] = MO_MOTION(which, 'd');
			rlist[ri++] = MO_MOTION(which, 'u');
		}
		else {
			rlist[ri++] = MO_MOTION(which, 'd');
			rlist[ri++] = MO_MOTION(which, 'u');
		}
		if (pi)
			mouse_motion_delay_release(which, true);
		else
			mouse_motion_delay_release(which, false);
		for (i = 0; (i != ri); ++i)
			manage_combos(megad, false, RCBM, rlist[i]);
		for (i = 0; (i != pi); ++i)
			manage_combos(megad, true, RCBM, plist[i]);
		if (calibrating) {
			for (i = 0; ((calibrating) && (i != pi)); ++i)
				manage_calibration(RCBM, plist[i]);
			break;
		}
		if (events != STARTED)
			break;
		for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) {
			// Release buttons first.
			for (i = 0; (i != ri); ++i) {
				if ((ctl->pressed == false) ||
				    ((uint32_t)(*ctl->rc)[RCBM] != rlist[i]))
					continue;
				ctl->pressed = false;
				ctl->coord = true;
				ctl->x = event.motion.x;
				ctl->y = event.motion.y;
				if ((ctl->release != NULL) &&
				    (ctl->release(*ctl, megad) == 0))
					return 0;
			}
			for (i = 0; (i != pi); ++i) {
				if ((uint32_t)(*ctl->rc)[RCBM] == plist[i]) {
					assert(ctl->press != NULL);
					ctl->pressed = true;
					ctl->coord = true;
					ctl->x = event.motion.x;
					ctl->y = event.motion.y;
					if (ctl->press(*ctl, megad) == 0)
						return 0;
				}
			}
		}
		for (i = 0; (i != ri); ++i)
			if (!manage_bindings(megad, false, RCBM, rlist[i]))
				return 0;
		for (i = 0; (i != pi); ++i)
			if (!manage_bindings(megad, true, RCBM, plist[i]))
				return 0;
		break;
	case SDL_MOUSEBUTTONDOWN:
		assert(event.button.state == SDL_PRESSED);
#ifdef WITH_DEBUGGER
		if (!debug_trap)
#endif
			mouse_grab(true);
		pressed = true;
		goto mouse_button;
	case SDL_MOUSEBUTTONUP:
		assert(event.button.state == SDL_RELEASED);
		pressed = false;
	mouse_button:
		mouse = MO_BUTTON(event.button.which, event.button.button);
		manage_combos(megad, pressed, RCBM, mouse);
		if (calibrating) {
			if (pressed)
				manage_calibration(RCBM, mouse);
			break;
		}
		if (events != STARTED)
			break;
		for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) {
			if ((*ctl->rc)[RCBM] != mouse)
				continue;
			ctl->pressed = pressed;
			ctl->coord = true;
			ctl->x = event.button.x;
			ctl->y = event.button.y;
			if (pressed == false) {
				if ((ctl->release != NULL) &&
				    (ctl->release(*ctl, megad) == 0))
					return 0;
			}
			else {
				assert(ctl->press != NULL);
				if (ctl->press(*ctl, megad) == 0)
					return 0;
			}
		}
		if (manage_bindings(megad, pressed, RCBM, mouse) == 0)
			return 0;
		break;
	case SDL_VIDEORESIZE:
		switch (screen_init(event.resize.w, event.resize.h)) {
		case 0:
			pd_message("Video resized to %ux%u.",
				   screen.surface->w,
				   screen.surface->h);
			break;
		case -1:
			pd_message("Failed to resize video to %ux%u.",
				   event.resize.w,
				   event.resize.h);
			break;
		default:
			fprintf(stderr,
				"sdl: fatal error while trying to change screen"
				" resolution.\n");
			return 0;
		}
		break;
	case SDL_QUIT:
		// We've been politely asked to exit, so let's leave
		return 0;
	default:
		break;
	}
	goto next_event;
}

static size_t pd_message_write(const char *msg, size_t len, unsigned int mark)
{
	uint8_t *buf = (screen.buf.u8 +
			(screen.pitch * (screen.height - screen.info_height)));
	size_t ret = 0;

	screen_lock();
	// Clear text area.
	memset(buf, 0x00, (screen.pitch * screen.info_height));
	// Write message.
	if (len != 0)
		ret = font_text(buf, screen.width, screen.info_height,
				screen.Bpp, screen.pitch, msg, len,
				mark, FONT_TYPE_AUTO);
	screen_unlock();
	return ret;
}

static size_t pd_message_display(const char *msg, size_t len,
				 unsigned int mark, bool update)
{
	size_t ret = pd_message_write(msg, len, mark);

	if (update)
		screen_update();
	if (len == 0)
		info.displayed = 0;
	else {
		info.displayed = 1;
		info.since = pd_usecs();
	}
	return ret;
}

/**
 * Process status bar message.
 */
static void pd_message_process(void)
{
	size_t len = info.length;
	size_t n;
	size_t r;

	if (len == 0) {
		pd_clear_message();
		return;
	}
	for (n = 0; (n < len); ++n)
		if (info.message[n] == '\n') {
			len = (n + 1);
			break;
		}
	r = pd_message_display(info.message, n, ~0u, false);
	if (r < n)
		len = r;
	memmove(info.message, &(info.message[len]), (info.length - len));
	info.length -= len;
}

/**
 * Postpone a message.
 */
static void pd_message_postpone(const char *msg)
{
	strncpy(&info.message[info.length], msg,
		(sizeof(info.message) - info.length));
	info.length = strlen(info.message);
	info.displayed = 1;
}

/**
 * Write a message to the status bar.
 */
void pd_message(const char *msg, ...)
{
	va_list vl;

	va_start(vl, msg);
	vsnprintf(info.message, sizeof(info.message), msg, vl);
	va_end(vl);
	info.length = strlen(info.message);
	pd_message_process();
}

void pd_clear_message()
{
	pd_message_display(NULL, 0, ~0u, false);
}

void pd_show_carthead(md& megad)
{
	struct {
		const char *p;
		const char *s;
		size_t len;
	} data[] = {
#define CE(i, s) { i, s, sizeof(s) }
		CE("System", megad.cart_head.system_name),
		CE("Copyright", megad.cart_head.copyright),
		CE("Domestic name", megad.cart_head.domestic_name),
		CE("Overseas name", megad.cart_head.overseas_name),
		CE("Product number", megad.cart_head.product_no),
		CE("Memo", megad.cart_head.memo),
		CE("Countries", megad.cart_head.countries)
	};
	size_t i;

	pd_message_postpone("\n");
	for (i = 0; (i < (sizeof(data) / sizeof(data[0]))); ++i) {
		char buf[256];
		size_t j, k;

		k = (size_t)snprintf(buf, sizeof(buf), "%s: ", data[i].p);
		if (k >= (sizeof(buf) - 1))
			continue;
		// Filter out extra spaces.
		for (j = 0; (j < data[i].len); ++j)
			if (isgraph(data[i].s[j]))
				break;
		if (j == data[i].len)
			continue;
		while ((j < data[i].len) && (k < (sizeof(buf) - 2))) {
			if (isgraph(data[i].s[j])) {
				buf[(k++)] = data[i].s[j];
				++j;
				continue;
			}
			buf[(k++)] = ' ';
			while ((j < data[i].len) && (!isgraph(data[i].s[j])))
				++j;
		}
		if (buf[(k - 1)] == ' ')
			--k;
		buf[k] = '\n';
		buf[(k + 1)] = '\0';
		pd_message_postpone(buf);
	}
}

/* Clean up this awful mess :) */
void pd_quit()
{
	size_t i;

#ifdef WITH_THREADS
	screen_update_thread_stop();
#endif
	if (mdscr.data) {
		free((void*)mdscr.data);
		mdscr.data = NULL;
	}
	SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
	pd_sound_deinit();
	if (mdpal)
		mdpal = NULL;
#ifdef WITH_OPENGL
	release_texture(screen.texture);
#endif
	free(filters_stack_data_buf[0].u8);
	free(filters_stack_data_buf[1].u8);
	assert(filters_stack_size <= elemof(filters_stack));
	assert(filters_stack_data[0].data == NULL);
	filters_stack_default = false;
	for (i = 0; (i != filters_stack_size); ++i) {
		free(filters_stack_data[i + 1].data);
		filters_stack_data[i + 1].data = NULL;
	}
	filters_stack_size = 0;
	SDL_Quit();
}
