RetroArch/tools/retroarch-joyconfig.c
2012-06-28 18:32:06 +02:00

388 lines
12 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2012 - Hans-Kristian Arntzen
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SDL
#include "SDL.h"
#endif
#include "conf/config_file.h"
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include "../boolean.h"
#include "general.h"
#include <assert.h>
#include "../compat/posix_string.h"
static int g_player = 1;
static int g_joypad = 0;
static char *g_in_path = NULL;
static char *g_out_path = NULL;
static bool g_use_misc = false;
static void print_help(void)
{
puts("==================");
puts("retroarch-joyconfig");
puts("==================");
puts("Usage: ssnes-joyconfig [ -p/--player <1-5> | -j/--joypad <num> | -i/--input <file> | -o/--output <file> | -h/--help ]");
puts("");
puts("-p/--player: Which player to configure for (1 up to and including 5).");
puts("-j/--joypad: Which joypad to use when configuring (first joypad is 0).");
puts("-i/--input: Input file to configure with. Binds will be added on or overwritten.");
puts("\tIf not selected, an empty config will be used as a base.");
puts("-o/--output: Output file to write to. If not selected, config file will be dumped to stdout.");
puts("-m/--misc: Also configure various keybinds that are not directly libretro related. These configurations are for player 1 only.");
puts("-h/--help: This help.");
}
struct bind
{
const char *keystr;
const char *confbtn[MAX_PLAYERS];
const char *confaxis[MAX_PLAYERS];
bool is_misc;
};
#define BIND(x, k) { x, { "input_player1_" #k "_btn", "input_player2_" #k "_btn", "input_player3_" #k "_btn", "input_player4_" #k "_btn", "input_player5_" #k "_btn" }, {"input_player1_" #k "_axis", "input_player2_" #k "_axis", "input_player3_" #k "_axis", "input_player4_" #k "_axis", "input_player5_" #k "_axis"}, false}
#define MISC_BIND(x, k) { x, { "input_" #k "_btn" }, { "input_" #k "_axis" }, true}
static struct bind binds[] = {
BIND("A button (right)", a),
BIND("B button (down)", b),
BIND("X button (top)", x),
BIND("Y button (left)", y),
BIND("L button (left shoulder)", l),
BIND("R button (right shoulder)", r),
BIND("L2 button (left shoulder #2)", l2),
BIND("R2 button (right shoulder #2)", r2),
BIND("L3 button (left analog button)", l3),
BIND("R3 button (right analog button)", r3),
BIND("Start button", start),
BIND("Select button", select),
BIND("Left D-pad", left),
BIND("Up D-pad", up),
BIND("Right D-pad", right),
BIND("Down D-pad", down),
BIND("Left analog X+ (right)", l_x_plus),
BIND("Left analog Y+ (down)", l_y_plus),
BIND("Left analog X- (left)", l_x_minus),
BIND("Left analog Y- (up)", l_y_minus),
BIND("Right analog X+ (right)", r_x_plus),
BIND("Right analog Y+ (down)", r_y_plus),
BIND("Right analog X- (left)", r_x_minus),
BIND("Right analog Y- (up)", r_y_minus),
MISC_BIND("Save state", save_state),
MISC_BIND("Load state", load_state),
MISC_BIND("Exit emulator", exit_emulator),
MISC_BIND("Toggle fullscreen", toggle_fullscreen),
MISC_BIND("Save state slot increase", state_slot_increase),
MISC_BIND("Save state slot decrease", state_slot_decrease),
MISC_BIND("Toggle fast forward", toggle_fast_forward),
MISC_BIND("Hold fast forward", hold_fast_forward),
MISC_BIND("Audio input rate step up", rate_step_up),
MISC_BIND("Audio input rate step down", rate_step_down),
MISC_BIND("Rewind", rewind),
MISC_BIND("Movie recording toggle", movie_record_toggle),
MISC_BIND("Pause", pause_toggle),
MISC_BIND("Frame advance", frame_advance),
MISC_BIND("Reset", reset),
MISC_BIND("Next shader", shader_next),
MISC_BIND("Previous shader", shader_prev),
MISC_BIND("Toggle cheat on/off", cheat_toggle),
MISC_BIND("Cheat index plus", cheat_index_plus),
MISC_BIND("Cheat index minus", cheat_index_minus),
MISC_BIND("Screenshot", screenshot),
MISC_BIND("DSP config", dsp_config),
MISC_BIND("Audio mute/unmute", audio_mute),
MISC_BIND("Netplay player flip", netplay_flip_players),
MISC_BIND("Slow motion", slowmotion),
};
static void get_binds(config_file_t *conf, int player, int joypad)
{
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_VIDEO) < 0)
{
fprintf(stderr, "Failed to init joystick subsystem.\n");
exit(1);
}
SDL_Joystick *joystick;
int num = SDL_NumJoysticks();
if (joypad >= num)
{
fprintf(stderr, "Cannot find joystick at index #%d, only have %d joystick(s) available ...\n", joypad, num);
exit(1);
}
joystick = SDL_JoystickOpen(joypad);
if (!joystick)
{
fprintf(stderr, "Cannot open joystick.\n");
exit(1);
}
int last_axis = -1;
bool block_axis = false;
int num_axes = SDL_JoystickNumAxes(joystick);
int *initial_axes = (int*)calloc(num_axes, sizeof(int));
assert(initial_axes);
SDL_PumpEvents();
SDL_JoystickUpdate();
for (int i = 0; i < num_axes; i++)
{
Sint16 initial = SDL_JoystickGetAxis(joystick, i);
if (abs(initial) < 20000)
initial = 0;
// Certain joypads (such as XBox360 controller on Linux) has a default negative axis for shoulder triggers,
// which makes configuration very awkward.
// If default negative, we can't trigger on the negative axis, and similar with defaulted positive axes.
if (initial)
fprintf(stderr, "Axis %d is defaulted to %s axis value of %d\n", i, initial > 0 ? "positive" : "negative", (int)initial);
initial_axes[i] = initial;
}
fprintf(stderr, "Configuring binds for player #%d on joypad #%d (%s)\n",
player + 1, joypad, SDL_JoystickName(joypad));
fprintf(stderr, "Press Ctrl-C to exit early.\n");
fprintf(stderr, "\n");
for (unsigned i = 0; i < sizeof(binds) / sizeof(struct bind) && (g_use_misc || !binds[i].is_misc) ; i++)
{
fprintf(stderr, "%s\n", binds[i].keystr);
bool done = false;
SDL_Event event;
int value;
const char *quark;
unsigned player_index = binds[i].is_misc ? 0 : player;
while (SDL_WaitEvent(&event) && !done)
{
switch (event.type)
{
case SDL_JOYBUTTONDOWN:
fprintf(stderr, "\tJoybutton pressed: %d\n", (int)event.jbutton.button);
done = true;
config_set_int(conf, binds[i].confbtn[player_index], event.jbutton.button);
break;
case SDL_JOYAXISMOTION:
{
bool same_axis = last_axis == event.jaxis.axis;
bool require_negative = initial_axes[event.jaxis.axis] > 0;
bool require_positive = initial_axes[event.jaxis.axis] < 0;
// Block the axis config until we're sure axes have returned to their neutral state.
if (same_axis)
{
if (abs(event.jaxis.value) < 10000 ||
(require_positive && event.jaxis.value < 0) ||
(require_negative && event.jaxis.value > 0))
block_axis = false;
}
// If axes are in their neutral state, we can't allow it.
if (require_negative && event.jaxis.value >= 0)
break;
if (require_positive && event.jaxis.value <= 0)
break;
if (block_axis)
break;
if (abs(event.jaxis.value) > 20000)
{
last_axis = event.jaxis.axis;
fprintf(stderr, "\tJoyaxis moved: Axis %d, Value %d\n", (int)event.jaxis.axis, (int)event.jaxis.value);
done = true;
block_axis = true;
char buf[8];
snprintf(buf, sizeof(buf), event.jaxis.value > 0 ? "+%d" : "-%d", event.jaxis.axis);
config_set_string(conf, binds[i].confaxis[player_index], buf);
}
break;
}
case SDL_KEYDOWN:
fprintf(stderr, ":V\n");
break;
case SDL_JOYHATMOTION:
value = event.jhat.value;
if (value & SDL_HAT_UP)
quark = "up";
else if (value & SDL_HAT_DOWN)
quark = "down";
else if (value & SDL_HAT_LEFT)
quark = "left";
else if (value & SDL_HAT_RIGHT)
quark = "right";
else
break;
fprintf(stderr, "\tJoyhat moved: Hat %d, direction %s\n", (int)event.jhat.hat, quark);
done = true;
char buf[16];
snprintf(buf, sizeof(buf), "h%d%s", event.jhat.hat, quark);
config_set_string(conf, binds[i].confbtn[player_index], buf);
break;
case SDL_QUIT:
goto end;
default:
break;
}
}
}
free(initial_axes);
end:
SDL_JoystickClose(joystick);
SDL_Quit();
}
static void parse_input(int argc, char *argv[])
{
char optstring[] = "i:o:p:j:hm";
struct option opts[] = {
{ "input", 1, NULL, 'i' },
{ "output", 1, NULL, 'o' },
{ "player", 1, NULL, 'p' },
{ "joypad", 1, NULL, 'j' },
{ "help", 0, NULL, 'h' },
{ "misc", 0, NULL, 'm' },
{ NULL, 0, NULL, 0 }
};
int option_index = 0;
for (;;)
{
int c = getopt_long(argc, argv, optstring, opts, &option_index);
if (c == -1)
break;
switch (c)
{
case 'h':
print_help();
exit(0);
case 'i':
g_in_path = strdup(optarg);
break;
case 'o':
g_out_path = strdup(optarg);
break;
case 'm':
g_use_misc = true;
break;
case 'j':
g_joypad = strtol(optarg, NULL, 0);
if (g_joypad < 0)
{
fprintf(stderr, "Joypad number can't be negative.\n");
exit(1);
}
break;
case 'p':
g_player = strtol(optarg, NULL, 0);
if (g_player < 1)
{
fprintf(stderr, "Player number must be at least 1.\n");
exit(1);
}
else if (g_player > MAX_PLAYERS)
{
fprintf(stderr, "Player number must be from 1 to %d.\n", MAX_PLAYERS);
exit(1);
}
break;
default:
break;
}
}
if (optind < argc)
{
print_help();
exit(1);
}
}
// Need SDL_main on OSX.
#ifndef __APPLE__
#undef main
#endif
int main(int argc, char *argv[])
{
parse_input(argc, argv);
config_file_t *conf = config_file_new(g_in_path);
if (!conf)
{
fprintf(stderr, "Couldn't open config file ...\n");
return 1;
}
const char *index_list[] = {
"input_player1_joypad_index",
"input_player2_joypad_index",
"input_player3_joypad_index",
"input_player4_joypad_index",
"input_player5_joypad_index",
"input_player6_joypad_index",
"input_player7_joypad_index",
"input_player8_joypad_index",
};
config_set_int(conf, index_list[g_player - 1], g_joypad);
get_binds(conf, g_player - 1, g_joypad);
config_file_write(conf, g_out_path);
config_file_free(conf);
if (g_in_path)
free(g_in_path);
if (g_out_path)
free(g_out_path);
return 0;
}