kolibrios-gitea/programs/emulator/dgen-sdl-1.33/sdl/sdl.cpp
turbocat ef78b78d1c DGen-SDL:
- The home directory is now taken from system.env;
- The original documentation was restored and converted into html;
- File path hints now work in command mode.

git-svn-id: svn://kolibrios.org@9970 a494cfbc-eb01-0410-851d-a64ba20cac60
2024-01-20 18:10:56 +00:00

6940 lines
166 KiB
C++

/**
* 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
#ifdef _KOLIBRI
#include <sys/ksys.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)
{
#ifndef _KOLIBRI
struct timeval tv;
gettimeofday(&tv, NULL);
return (unsigned long)((tv.tv_sec * 1000000) + tv.tv_usec);
#else
return _ksys_get_tick_count()*10000;
#endif
}
/**
* 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;
}
#if !defined __MINGW32__ && !defined _KOLIBRI
// [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"));
#if !defined __MINGW32__ && !defined _KOLIBRI
// 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;
#endif
bool pressed;
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();
}