/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - 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 . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "conf/config_file.h" #include #include #include "../compat/getopt_rarch.h" #include "../boolean.h" #include "../input/input_common.h" #include "../general.h" #include #include "../compat/posix_string.h" // Need to be present for build to work, but it's not *really* used. // Better than having to build special versions of lots of objects with special #ifdefs. struct settings g_settings; struct global g_extern; driver_t driver; static int g_player = 1; static int g_joypad = 0; static int g_timeout = 0; static char *g_in_path = NULL; static char *g_out_path = NULL; static char *g_auto_path = NULL; static char *g_driver = NULL; static unsigned g_meta_level = 0; static void print_help(void) { puts("====================="); puts(" retroarch-joyconfig"); puts("====================="); puts("Usage: retroarch-joyconfig [ options ... ]"); puts(""); puts("-p/--player: Which player to configure for (1 up to and including 8)."); 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("-a/--autoconfig: Outputs an autoconfig file for joypad which was configured."); puts("-M/--allmisc: Also configure various keybinds that are not directly libretro related."); puts("\tThese configurations are for player 1 only."); puts("-m/--misc: Same as --allmisc, but exposes a smaller subset of misc binds which are deemed most useful for regular use."); puts("-t/--timeout: Adds a timeout of N seconds to each bind. If timed out, the bind will not be used."); puts("-d/--driver: Uses a specific joypad driver."); puts("-h/--help: This help."); } #define MAX_BUTTONS 32 #define MAX_AXES 32 #define MAX_HATS 32 struct poll_data { bool buttons[MAX_BUTTONS]; int16_t axes[MAX_AXES]; uint16_t hats[MAX_HATS]; }; static void poll_joypad(const rarch_joypad_driver_t *driver, unsigned pad, struct poll_data *data) { unsigned i; if (driver) driver->poll(); for (i = 0; i < MAX_BUTTONS; i++) data->buttons[i] = input_joypad_button_raw(driver, pad, i); for (i = 0; i < MAX_AXES; i++) data->axes[i] = input_joypad_axis_raw(driver, pad, i); for (i = 0; i < MAX_HATS; i++) { uint16_t hat = 0; hat |= input_joypad_hat_raw(driver, pad, HAT_UP_MASK, i) << HAT_UP_SHIFT; hat |= input_joypad_hat_raw(driver, pad, HAT_DOWN_MASK, i) << HAT_DOWN_SHIFT; hat |= input_joypad_hat_raw(driver, pad, HAT_LEFT_MASK, i) << HAT_LEFT_SHIFT; hat |= input_joypad_hat_raw(driver, pad, HAT_RIGHT_MASK, i) << HAT_RIGHT_SHIFT; data->hats[i] = hat; } } static void get_binds(config_file_t *conf, config_file_t *auto_conf, int player, int joypad) { int i, timeout_cnt; const rarch_joypad_driver_t *driver = input_joypad_init_driver(g_driver); if (!driver) { fprintf(stderr, "Cannot find any valid input driver.\n"); exit(1); } if (!driver->query_pad(joypad)) { fprintf(stderr, "Couldn't open joystick #%u.\n", joypad); exit(1); } fprintf(stderr, "Found joypad driver: %s\n", driver->ident); const char *joypad_name = input_joypad_name(driver, joypad); fprintf(stderr, "Using joypad: %s\n", joypad_name ? joypad_name : "Unknown"); if (joypad_name && auto_conf) { config_set_string(auto_conf, "input_device", joypad_name); config_set_string(auto_conf, "input_driver", driver->ident); } int16_t initial_axes[MAX_AXES] = {0}; struct poll_data old_poll = {{0}}; struct poll_data new_poll = {{0}}; int last_axis = -1; bool block_axis = false; int timeout_ticks = g_timeout * 100; poll_joypad(driver, joypad, &old_poll); fprintf(stderr, "\nJoypads tend to have stale state after opened.\nPress some buttons and move some axes around to make sure joypad state is completely neutral before proceeding.\nWhen done, press Enter ... "); getchar(); poll_joypad(driver, joypad, &old_poll); for (i = 0; i < MAX_AXES; i++) { int16_t initial = input_joypad_axis_raw(driver, joypad, 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", initial); initial_axes[i] = initial; } for (i = 0; i < MAX_BUTTONS; i++) { if (old_poll.buttons[i]) fprintf(stderr, "Button %d was initially pressed. This indicates broken initial state.\n", i); } fprintf(stderr, "Configuring binds for player #%d on joypad #%d.\n\n", player + 1, joypad); for (i = 0, timeout_cnt = 0; input_config_bind_map[i].valid; i++, timeout_cnt = 0) { int j; if (i == RARCH_TURBO_ENABLE) continue; unsigned meta_level = input_config_bind_map[i].meta; if (meta_level > g_meta_level) continue; fprintf(stderr, "%s\n", input_config_bind_map[i].desc); unsigned player_index = input_config_bind_map[i].meta ? 0 : player; for (;;) { old_poll = new_poll; // To avoid pegging CPU. // Ideally use an event-based joypad scheme, // but it adds far more complexity, so, meh. rarch_sleep(10); if (timeout_ticks) { timeout_cnt++; if (timeout_cnt >= timeout_ticks) { fprintf(stderr, "\tTimed out ...\n"); break; } } poll_joypad(driver, joypad, &new_poll); for (j = 0; j < MAX_BUTTONS; j++) { if (new_poll.buttons[j] && !old_poll.buttons[j]) { fprintf(stderr, "\tJoybutton pressed: %u\n", j); char key[64]; snprintf(key, sizeof(key), "%s_%s_btn", input_config_get_prefix(player_index, input_config_bind_map[i].meta), input_config_bind_map[i].base); config_set_int(conf, key, j); if (auto_conf) { snprintf(key, sizeof(key), "input_%s_btn", input_config_bind_map[i].base); config_set_int(auto_conf, key, j); } goto out; } } for (j = 0; j < MAX_AXES; j++) { if (new_poll.axes[j] == old_poll.axes[j]) continue; int16_t value = new_poll.axes[j]; bool same_axis = last_axis == j; bool require_negative = initial_axes[j] > 0; bool require_positive = initial_axes[j] < 0; // Block the axis config until we're sure axes have returned to their neutral state. if (same_axis) { if (abs(value) < 10000 || (require_positive && value < 0) || (require_negative && value > 0)) block_axis = false; } // If axes are in their neutral state, we can't allow it. if (require_negative && value >= 0) continue; if (require_positive && value <= 0) continue; if (block_axis) continue; if (abs(value) > 20000) { last_axis = j; fprintf(stderr, "\tJoyaxis moved: Axis %d, Value %d\n", j, value); char buf[8]; snprintf(buf, sizeof(buf), value > 0 ? "+%d" : "-%d", j); char key[64]; snprintf(key, sizeof(key), "%s_%s_axis", input_config_get_prefix(player_index, input_config_bind_map[i].meta), input_config_bind_map[i].base); config_set_string(conf, key, buf); if (auto_conf) { snprintf(key, sizeof(key), "input_%s_axis", input_config_bind_map[i].base); config_set_string(auto_conf, key, buf); } block_axis = true; goto out; } } for (j = 0; j < MAX_HATS; j++) { const char *quark = NULL; uint16_t value = new_poll.hats[j]; uint16_t old_value = old_poll.hats[j]; if ((value & HAT_UP_MASK) && !(old_value & HAT_UP_MASK)) quark = "up"; else if ((value & HAT_LEFT_MASK) && !(old_value & HAT_LEFT_MASK)) quark = "left"; else if ((value & HAT_RIGHT_MASK) && !(old_value & HAT_RIGHT_MASK)) quark = "right"; else if ((value & HAT_DOWN_MASK) && !(old_value & HAT_DOWN_MASK)) quark = "down"; if (quark) { fprintf(stderr, "\tJoyhat moved: Hat %d, direction %s\n", j, quark); char buf[16]; snprintf(buf, sizeof(buf), "h%d%s", j, quark); char key[64]; snprintf(key, sizeof(key), "%s_%s_btn", input_config_get_prefix(player_index, input_config_bind_map[i].meta), input_config_bind_map[i].base); config_set_string(conf, key, buf); if (auto_conf) { snprintf(key, sizeof(key), "input_%s_btn", input_config_bind_map[i].base); config_set_string(auto_conf, key, buf); } goto out; } } } out: old_poll = new_poll; } } static void parse_input(int argc, char *argv[]) { char optstring[] = "i:o:a:p:j:t:hmMd:"; struct option opts[] = { { "input", 1, NULL, 'i' }, { "output", 1, NULL, 'o' }, { "autoconfig", 1, NULL, 'a' }, { "player", 1, NULL, 'p' }, { "joypad", 1, NULL, 'j' }, { "help", 0, NULL, 'h' }, { "misc", 0, NULL, 'm' }, { "allmisc", 0, NULL, 'M' }, { "timeout", 1, NULL, 't' }, { "driver", 1, NULL, 'd' }, { 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 't': g_timeout = strtol(optarg, NULL, 0); break; case 'd': g_driver = strdup(optarg); break; case 'o': g_out_path = strdup(optarg); break; case 'a': g_auto_path = strdup(optarg); break; case 'm': g_meta_level = 1; break; case 'M': g_meta_level = 2; 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); config_file_t *auto_conf = NULL; if (g_auto_path) auto_conf = config_file_new(NULL); get_binds(conf, auto_conf, g_player - 1, g_joypad); config_file_write(conf, g_out_path); config_file_free(conf); if (auto_conf) { fprintf(stderr, "Writing autoconfig profile to: %s.\n", g_auto_path); config_file_write(auto_conf, g_auto_path); config_file_free(auto_conf); } free(g_in_path); free(g_out_path); free(g_auto_path); return 0; }