mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
537 lines
12 KiB
C++
537 lines
12 KiB
C++
/*
|
|
NEStopia / Linux
|
|
Port by R. Belmont
|
|
|
|
controlconfig.cpp - controller configuration utility.
|
|
|
|
Based on a patch by David B. Robins.
|
|
Font code and data by Duddie.
|
|
*/
|
|
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <strstream>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <SDL.h>
|
|
#include <string.h>
|
|
#include <cassert>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
#include "kentry.h"
|
|
#include "input.h"
|
|
#include "controlconfig.h"
|
|
|
|
#include "fonts.inc"
|
|
|
|
static SDL_Surface *config_screen;
|
|
|
|
static const int win_w = 360;
|
|
static const int win_h = 64;
|
|
|
|
struct ConfigItem
|
|
{
|
|
const KEntry *table;
|
|
int taboffs;
|
|
int player;
|
|
};
|
|
|
|
// Order MUST match the items in the config popup
|
|
static ConfigItem config[] =
|
|
{
|
|
{ controlcodes, 0, 1 }, // p1 up
|
|
{ controlcodes, 1, 1 }, // p1 down
|
|
{ controlcodes, 2, 1 }, // p1 left
|
|
{ controlcodes, 3, 1 }, // p1 right
|
|
{ controlcodes, 4, 1 }, // p1 a
|
|
{ controlcodes, 5, 1 }, // p1 b
|
|
{ controlcodes, 6, 1 }, // p1 start
|
|
{ controlcodes, 7, 1 }, // p1 select
|
|
{ controlcodes, 0, 2 }, // p2 up
|
|
{ controlcodes, 1, 2 }, // p2 down
|
|
{ controlcodes, 2, 2 }, // p2 left
|
|
{ controlcodes, 3, 2 }, // p2 right
|
|
{ controlcodes, 4, 2 }, // p2 a
|
|
{ controlcodes, 5, 2 }, // p2 b
|
|
{ controlcodes, 6, 2 }, // p2 start
|
|
{ controlcodes, 7, 2 }, // p2 select
|
|
{ metacodes, 7, 0 }, // movie save
|
|
{ metacodes, 8, 0 }, // movie load
|
|
{ metacodes, 9, 0 }, // movie stop
|
|
{ metacodes, 0, 0 }, // reset
|
|
{ metacodes, 10, 0 }, // FDS flip
|
|
{ metacodes, 5, 0 }, // save state
|
|
{ metacodes, 6, 0 }, // load state
|
|
{ metacodes, 11, 0 }, // fullscreen toggle
|
|
{ metacodes, 14, 0 }, // stop
|
|
{ metacodes, 15, 0 }, // exit
|
|
{ metacodes, 12, 0 }, // rewinder start
|
|
{ metacodes, 13, 0 }, // rewinder stop
|
|
{ metacodes, 1, 0 }, // quicksave slot 1
|
|
{ metacodes, 3, 0 }, // quicksave slot 2
|
|
{ metacodes, 2, 0 }, // quickload slot 1
|
|
{ metacodes, 4, 0 }, // quickload slot 2
|
|
{ metacodes, 16, 0 }, // Vs. coin 1
|
|
{ metacodes, 17, 0 }, // Vs. coin 2
|
|
};
|
|
|
|
// represents an axis that is not centered in the "dead zone"
|
|
struct AxisNoise
|
|
{
|
|
int which;
|
|
int axis;
|
|
Sint16 pos;
|
|
|
|
AxisNoise(int _which, int _axis, Sint16 _pos) :
|
|
which(_which), axis(_axis), pos(_pos) {}
|
|
|
|
bool match(const SDL_Event &evt) const
|
|
{
|
|
assert(evt.type == SDL_JOYAXISMOTION);
|
|
return evt.jaxis.which == which && evt.jaxis.axis == axis
|
|
&& abs(evt.jaxis.value - pos) <= DEADZONE;
|
|
}
|
|
};
|
|
|
|
typedef std::vector<AxisNoise> AxisNoiseVec;
|
|
static AxisNoiseVec axisNoise;
|
|
|
|
struct EventMatch
|
|
{
|
|
SDL_Event evt;
|
|
int len; // of the user text
|
|
std::string code; // code for the nstcontrols file
|
|
|
|
EventMatch(const SDL_Event &_evt, int _len, const char *_code) :
|
|
evt(_evt), len(_len), code(_code) {}
|
|
};
|
|
typedef std::vector<EventMatch> EventVec;
|
|
|
|
|
|
// Put a glyph at the specified coordinates (surface must be locked!)
|
|
static inline void _putglyph(char *p, int Bpp, int pitch, char which)
|
|
{
|
|
int *glyph = system_font[which & 0x7f];
|
|
int x = 0, i;
|
|
|
|
for(; *glyph != -1; ++glyph)
|
|
{
|
|
p += (((x += *glyph) >> 3) * pitch); x &= 7;
|
|
for(i = 0; i < Bpp; ++i) p[(x * Bpp) + i] = 0xff;
|
|
}
|
|
}
|
|
|
|
void sdl_locksurf()
|
|
{
|
|
if (SDL_MUSTLOCK(config_screen))
|
|
{
|
|
SDL_LockSurface(config_screen);
|
|
}
|
|
}
|
|
|
|
void sdl_unlockandflip()
|
|
{
|
|
if(SDL_MUSTLOCK(config_screen))
|
|
{
|
|
SDL_UnlockSurface(config_screen);
|
|
}
|
|
|
|
SDL_Flip(config_screen);
|
|
}
|
|
|
|
void sdl_clear()
|
|
{
|
|
memset((char *)config_screen->pixels, 0, config_screen->pitch * win_h);
|
|
}
|
|
|
|
// This writes a string of text at the given x and y coordinates
|
|
void sdl_font_text(int x, int y, const char *message)
|
|
{
|
|
int pitch = config_screen->pitch, Bpp = config_screen->format->BytesPerPixel;
|
|
char *p = (char*)config_screen->pixels + (pitch * y) + (Bpp * x);
|
|
|
|
for(; *message; p += (8 * Bpp), ++message)
|
|
_putglyph(p, Bpp, pitch, *message);
|
|
}
|
|
|
|
// check for noisy axis that shouldn't count as motion
|
|
inline static bool noisy_axis(const SDL_Event &evt)
|
|
{
|
|
for (AxisNoiseVec::const_iterator i = axisNoise.begin();
|
|
i != axisNoise.end(); ++i)
|
|
{
|
|
if (i->match(evt))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// have we already used this input (or axis) for this event?
|
|
// (it's OK to assign the same key to multiple events, just not the same one!)
|
|
static bool match_event(const EventVec &evtvec, const SDL_Event &evt)
|
|
{
|
|
for (EventVec::const_iterator i = evtvec.begin(); i != evtvec.end(); ++i)
|
|
{
|
|
const SDL_Event &evtT = i->evt;
|
|
if (evtT.type != evt.type)
|
|
continue;
|
|
switch (evtT.type)
|
|
{
|
|
case SDL_KEYDOWN:
|
|
if (evtT.key.keysym.sym == evt.key.keysym.sym)
|
|
return true;
|
|
break;
|
|
|
|
case SDL_JOYBUTTONDOWN:
|
|
if (evtT.jbutton.which == evt.jbutton.which
|
|
&& evtT.jbutton.button == evt.jbutton.button)
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case SDL_JOYHATMOTION:
|
|
if (evtT.jhat.which == evt.jhat.which
|
|
&& evtT.jhat.hat == evt.jhat.hat)
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case SDL_JOYAXISMOTION:
|
|
if (evtT.jaxis.which == evt.jaxis.which
|
|
&& evtT.jaxis.axis == evt.jaxis.axis)
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// read an event from SDL and process it accordingly
|
|
static void read_event(InputDefT *ctl_list, const char *entry, const char *desc = NULL)
|
|
{
|
|
char descstr[128], secs[32], curmatch[64];
|
|
char lastdir = 0;
|
|
int seconds = 5;
|
|
int quit = 0;
|
|
EventVec evtvec; // input events already seen
|
|
unsigned int basems, nextms;
|
|
|
|
if (!desc)
|
|
{
|
|
desc = entry;
|
|
}
|
|
|
|
sprintf(secs, "Time left: %d", seconds);
|
|
|
|
evtvec.clear();
|
|
|
|
sdl_locksurf();
|
|
sdl_clear();
|
|
sprintf(descstr, "Press key or joy button/axis for: %s", desc);
|
|
sdl_font_text(0, 0, descstr);
|
|
sdl_font_text(0, 48, secs);
|
|
sdl_unlockandflip();
|
|
|
|
// use to ensure we don't record multiple events for same axis movement
|
|
static struct
|
|
{
|
|
time_t t; // time when movement counts as new
|
|
int which; // which joystick?
|
|
int axis; // which axis?
|
|
char dir; // direction ('+' or '-')
|
|
}
|
|
lastAxis;
|
|
|
|
basems = SDL_GetTicks();
|
|
nextms = basems + 1000;
|
|
curmatch[0] = '\0';
|
|
|
|
while (!quit)
|
|
{
|
|
SDL_Event evt;
|
|
char match[20] = { 0 };
|
|
char input[20] = { 0 };
|
|
|
|
while (SDL_PollEvent(&evt))
|
|
{
|
|
// if we've already seen this input, ignore it
|
|
if (match_event(evtvec, evt))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (evt.type)
|
|
{
|
|
case SDL_KEYDOWN:
|
|
{
|
|
int ksym = int(evt.key.keysym.sym);
|
|
const char *key = kentry_find_code(keycodes, ksym);
|
|
|
|
if (key != NULL)
|
|
{
|
|
sprintf(input, "_%s", key);
|
|
}
|
|
else if (isprint(ksym))
|
|
{
|
|
sprintf(input, "%c", ksym);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "\a"); // beep
|
|
break;
|
|
}
|
|
sprintf(match, "%s", SDL_GetKeyName(SDLKey(ksym)));
|
|
}
|
|
quit = 1;
|
|
break;
|
|
|
|
case SDL_JOYAXISMOTION:
|
|
if (abs(evt.jaxis.value) > DEADZONE)
|
|
{
|
|
// check for a noisy axis with bad centering
|
|
if (noisy_axis(evt))
|
|
break;
|
|
|
|
char dir = (evt.jaxis.value) > 0 ? '+' : '-';
|
|
|
|
time_t t = time(NULL);
|
|
|
|
/*if (lastAxis.dir == dir && lastAxis.which == evt.jaxis.which
|
|
&& lastAxis.axis == evt.jaxis.axis && t < lastAxis.t)
|
|
{
|
|
break;
|
|
}*/
|
|
|
|
lastAxis.dir = dir;
|
|
lastAxis.which = evt.jaxis.which;
|
|
lastAxis.axis = evt.jaxis.axis;
|
|
lastAxis.t = t + 2;
|
|
|
|
sprintf(match, "joy %d axis %d %c",
|
|
evt.jaxis.which + 1, evt.jaxis.axis, dir);
|
|
sprintf(input, "_J%dA%d%c", evt.jaxis.which, evt.jaxis.axis, dir);
|
|
}
|
|
quit = 1;
|
|
break;
|
|
|
|
case SDL_JOYHATMOTION:
|
|
sprintf(match, "joy %d hat %d",
|
|
evt.jhat.which + 1, evt.jhat.value);
|
|
sprintf(input, "_J%dH%d%d", evt.jbutton.which, evt.jhat.hat, evt.jhat.value);
|
|
quit = 1;
|
|
break;
|
|
|
|
case SDL_JOYBUTTONDOWN:
|
|
sprintf(match, "joy %d button %d",
|
|
evt.jbutton.which + 1, evt.jbutton.button);
|
|
sprintf(input, "_J%dB%d", evt.jbutton.which, evt.jbutton.button);
|
|
quit = 1;
|
|
break;
|
|
|
|
case SDL_QUIT:
|
|
return;
|
|
break;
|
|
}
|
|
|
|
if (strlen(match) > 0)
|
|
{
|
|
evtvec.clear();
|
|
evtvec.push_back(EventMatch(evt, strlen(match), input));
|
|
|
|
match[0] = '\0';
|
|
for (EventVec::const_iterator i = evtvec.begin(); i != evtvec.end(); ++i)
|
|
{
|
|
strcat(match, i->code.c_str());
|
|
strcat(match, " ");
|
|
}
|
|
|
|
if (match[0])
|
|
{
|
|
sdl_locksurf();
|
|
sdl_clear();
|
|
sdl_font_text(0, 0, descstr);
|
|
sdl_font_text(0, 20, match);
|
|
sdl_font_text(0, 48, secs);
|
|
sdl_unlockandflip();
|
|
|
|
strcpy(curmatch, match);
|
|
}
|
|
}
|
|
}
|
|
|
|
basems = SDL_GetTicks();
|
|
if (basems >= nextms)
|
|
{
|
|
seconds--;
|
|
if (seconds)
|
|
{
|
|
nextms = basems + 1000;
|
|
|
|
sprintf(secs, "Time left: %d", seconds);
|
|
|
|
sdl_locksurf();
|
|
sdl_clear();
|
|
sdl_font_text(0, 0, descstr);
|
|
|
|
if (curmatch[0])
|
|
{
|
|
sdl_font_text(0, 20, curmatch);
|
|
}
|
|
sdl_font_text(0, 48, secs);
|
|
sdl_unlockandflip();
|
|
}
|
|
else
|
|
{
|
|
quit = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (evtvec.size() > 0)
|
|
{
|
|
char outline[128], tmp[32];
|
|
InputDefT newcontrol;
|
|
int icontrol = 0;
|
|
|
|
// write the nstcontrols line
|
|
sprintf(outline, "%s\t", desc);
|
|
|
|
sprintf(tmp, "%s\t", evtvec[0].code.c_str());
|
|
strcat(outline, tmp);
|
|
|
|
// sanitize new control entry going in
|
|
memset(&newcontrol, 0, sizeof(newcontrol));
|
|
|
|
if (translate_event(outline, &newcontrol, icontrol, 1))
|
|
{
|
|
int j = 0;
|
|
|
|
while (ctl_list[j].player != -1)
|
|
{
|
|
// if this is the same event type, player number, and bound control, replace it
|
|
if ((ctl_list[j].evt.type == newcontrol.evt.type) && (ctl_list[j].player == newcontrol.player) && (ctl_list[j].codeout == newcontrol.codeout))
|
|
{
|
|
memcpy(&ctl_list[j], &newcontrol, sizeof(InputDefT));
|
|
return;
|
|
}
|
|
|
|
j++;
|
|
}
|
|
|
|
// no existing entry was a match, append it to the list if there's room
|
|
if (j < 128)
|
|
{
|
|
memcpy(&ctl_list[j], &newcontrol, sizeof(InputDefT));
|
|
ctl_list[j+1].player = -1;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Controls list too large, cannot add new control!\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void run_configurator(InputDefT *ctl_list, int itemToSet, int usejoys)
|
|
{
|
|
|
|
unsetenv("SDL_WINDOWID");
|
|
|
|
SDL_Joystick *joy[10];
|
|
|
|
// set up SDL
|
|
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_JOYSTICK))
|
|
{
|
|
std::cout << "Unable to init SDL\n";
|
|
return;
|
|
}
|
|
|
|
// set up joysticks
|
|
if (usejoys)
|
|
{
|
|
int cjoy = SDL_NumJoysticks();
|
|
//fprintf(stderr, "%d joystick(s) detected.\n", cjoy);
|
|
for (int ijoy = 0; ijoy < cjoy; ++ijoy)
|
|
{
|
|
if (ijoy < 10)
|
|
{
|
|
joy[ijoy] = SDL_JoystickOpen(ijoy);
|
|
int caxis = SDL_JoystickNumAxes(joy[ijoy]);
|
|
int chat = SDL_JoystickNumHats(joy[ijoy]);
|
|
//fprintf(stderr, "Joystick %d is '%s' has %d axes, %d hat(s), and %d buttons.\n",
|
|
// ijoy + 1, SDL_JoystickName(ijoy), caxis, chat, SDL_JoystickNumButtons(joy[ijoy]));
|
|
|
|
// calibrate - read axis positions and store as "centered"
|
|
// we expect joysticks to be centered in (-DEADZONE, DEADZONE) but
|
|
// need to filter out noise
|
|
for (int iaxis = 0; iaxis < caxis; ++iaxis)
|
|
{
|
|
Sint16 pos = SDL_JoystickGetAxis(joy[ijoy], iaxis);
|
|
if (abs(pos) > DEADZONE)
|
|
axisNoise.push_back(AxisNoise(ijoy, iaxis, pos));
|
|
}
|
|
}
|
|
}
|
|
SDL_JoystickEventState(SDL_ENABLE);
|
|
}
|
|
//fprintf(stderr, "\n");
|
|
|
|
// we need to set a video mode to get keyboard events
|
|
if (! (config_screen = SDL_SetVideoMode(336, 80, 8, 0 | SDL_NOFRAME)))
|
|
//screen = SDL_SetVideoMode(420, 200, 8, 0);
|
|
{
|
|
std::cout << "SDL_SetVideoMode failed: %s\n";
|
|
exit(1);
|
|
}
|
|
|
|
SDL_WM_SetCaption("Configure control", "nst");
|
|
|
|
if (config[itemToSet].table == controlcodes)
|
|
{
|
|
std::string prefix = std::string("P") + std::string(1, '0' + config[itemToSet].player);
|
|
const KEntry *ctrl = &config[itemToSet].table[config[itemToSet].taboffs];
|
|
|
|
std::string entry = prefix + ctrl->string;
|
|
read_event(ctl_list, entry.c_str());
|
|
}
|
|
else
|
|
{
|
|
const KEntry *meta = &config[itemToSet].table[config[itemToSet].taboffs];
|
|
|
|
read_event(ctl_list, "META", meta->string);
|
|
}
|
|
|
|
//fprintf(stderr, "\n");
|
|
//printf("Test\n");
|
|
|
|
if (usejoys)
|
|
{
|
|
if (SDL_NumJoysticks() > 0)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < SDL_NumJoysticks(); i++)
|
|
{
|
|
// we only? support 10 joysticks
|
|
if (i < 10)
|
|
{
|
|
SDL_JoystickClose(joy[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// cleanup SDL
|
|
SDL_Quit();
|
|
}
|