diff --git a/docs/retroarch.1 b/docs/retroarch.1 index 9716ad8846..ede5337d1c 100644 --- a/docs/retroarch.1 +++ b/docs/retroarch.1 @@ -133,31 +133,17 @@ If set explicitly here, overrides config file for that port. Disconnects an input device from port number PORT. Possible values for PORT are 1 to 8. This may be needed for some odd games to run properly. If set explicitly here, overrides config file for that port. -.TP -\fB--scope, -p\fR -Connects a Super Scope into port 2 of an emulated SNES. It is controlled with your mouse. -If set explicitly here, overrides config file for that port. - -.TP -\fB--justifier, -j\fR -Connects a Konami Justifier into port 2 of an emulated SNES. It is controlled with your mouse. -If set explicitly here, overrides config file for that port. - -.TP -\fB--justifiers, -J\fR -Connects two Konami Justifier into port 2 of an emulated SNES. Currently, only player 1 is controlled with the mouse. -If set explicitly here, overrides config file for that port. - -.TP -\fB--multitap, -4\fR -Connects a four-way multitap into an emulated SNES. This allows for up to 5 players. -If set explicitly here, overrides config file for that port. - .TP \fB--dualanalog PORT, -A PORT\fR Connects a DualAnalog controller into port PORT. Possible values are 1 to 8. If set explicitly here, overrides config file for that port. +.TP +\fB--device PORT:ID, -d PORT:ID\fR +Connects a generic input device ID into port PORT. Possible values for port are 1 to 8. +If set explicitly here, overrides config file for that port. +ID is an unsigned number corresponding to the device for a libretro core. + .TP \fB--record PATH, -r PATH\fR Activates video recording of gameplay into PATH. Using .mkv extension is recommended. diff --git a/dynamic.c b/dynamic.c index 08f8e21ba4..1d9c96f0cf 100644 --- a/dynamic.c +++ b/dynamic.c @@ -254,6 +254,18 @@ const struct retro_subsystem_info *libretro_find_subsystem_info(const struct ret return NULL; } +const struct retro_controller_description *libretro_find_controller_description(const struct retro_controller_info *info, unsigned id) +{ + unsigned i; + for (i = 0; i < info->num_types; i++) + { + if (info->types[i].id == id) + return &info->types[i]; + } + + return NULL; +} + static void load_symbols(bool is_dummy) { if (is_dummy) @@ -434,6 +446,7 @@ void uninit_libretro_sym(void) // No longer valid. free(g_extern.system.special); + free(g_extern.system.ports); memset(&g_extern.system, 0, sizeof(g_extern.system)); #ifdef HAVE_CAMERA g_extern.camera_active = false; @@ -907,16 +920,6 @@ bool rarch_environment_cb(unsigned cmd, void *data) break; } - // Private extensions for internal use, not part of libretro API. - case RETRO_ENVIRONMENT_SET_LIBRETRO_PATH: - RARCH_LOG("Environ (Private) SET_LIBRETRO_PATH.\n"); - - if (path_file_exists((const char*)data)) - strlcpy(g_settings.libretro, (const char*)data, sizeof(g_settings.libretro)); - else - return false; - break; - case RETRO_ENVIRONMENT_GET_CONTENT_DIRECTORY: { const char **dir = (const char**)data; @@ -959,6 +962,38 @@ bool rarch_environment_cb(unsigned cmd, void *data) break; } + case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO: + { + RARCH_LOG("Environ SET_CONTROLLER_INFO.\n"); + unsigned i, j; + const struct retro_controller_info *info = (const struct retro_controller_info*)data; + for (i = 0; info[i].types; i++) + { + RARCH_LOG("Controller port: %u\n", i); + for (j = 0; j < info[i].num_types; j++) + RARCH_LOG(" %s (ident: %s, ID: %u)\n", info[i].types[j].desc, info[i].types[j].ident, info[i].types[j].id); + } + + free(g_extern.system.ports); + g_extern.system.ports = (struct retro_controller_info*)calloc(i, sizeof(*g_extern.system.ports)); + if (!g_extern.system.ports) + return false; + + memcpy(g_extern.system.ports, info, i * sizeof(*g_extern.system.ports)); + g_extern.system.num_ports = i; + break; + } + + // Private extensions for internal use, not part of libretro API. + case RETRO_ENVIRONMENT_SET_LIBRETRO_PATH: + RARCH_LOG("Environ (Private) SET_LIBRETRO_PATH.\n"); + + if (path_file_exists((const char*)data)) + strlcpy(g_settings.libretro, (const char*)data, sizeof(g_settings.libretro)); + else + return false; + break; + case RETRO_ENVIRONMENT_EXEC: case RETRO_ENVIRONMENT_EXEC_ESCAPE: diff --git a/dynamic.h b/dynamic.h index 9f5996c110..68494516dc 100644 --- a/dynamic.h +++ b/dynamic.h @@ -65,6 +65,7 @@ void libretro_free_system_info(struct retro_system_info *info); void libretro_get_current_core_pathname(char *name, size_t size); const struct retro_subsystem_info *libretro_find_subsystem_info(const struct retro_subsystem_info *info, unsigned num_info, const char *ident); +const struct retro_controller_description *libretro_find_controller_description(const struct retro_controller_info *info, unsigned id); extern void (*pretro_init)(void); extern void (*pretro_deinit)(void); diff --git a/frontend/menu/menu_settings.c b/frontend/menu/menu_settings.c index 87ab571675..96c6ecffe1 100644 --- a/frontend/menu/menu_settings.c +++ b/frontend/menu/menu_settings.c @@ -934,10 +934,6 @@ int menu_set_settings(void *data, unsigned setting, unsigned action) RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ANALOG, RETRO_DEVICE_MOUSE, - RETRO_DEVICE_JOYPAD_MULTITAP, - RETRO_DEVICE_LIGHTGUN_SUPER_SCOPE, - RETRO_DEVICE_LIGHTGUN_JUSTIFIER, - RETRO_DEVICE_LIGHTGUN_JUSTIFIERS, }; unsigned current_device, current_index, i; @@ -2211,11 +2207,7 @@ void menu_set_settings_label(char *type_str, size_t type_str_size, unsigned *w, case RETRO_DEVICE_NONE: name = "None"; break; case RETRO_DEVICE_JOYPAD: name = "Joypad"; break; case RETRO_DEVICE_ANALOG: name = "Joypad w/ Analog"; break; - case RETRO_DEVICE_JOYPAD_MULTITAP: name = "Multitap"; break; case RETRO_DEVICE_MOUSE: name = "Mouse"; break; - case RETRO_DEVICE_LIGHTGUN_JUSTIFIER: name = "Justifier"; break; - case RETRO_DEVICE_LIGHTGUN_JUSTIFIERS: name = "Justifiers"; break; - case RETRO_DEVICE_LIGHTGUN_SUPER_SCOPE: name = "SuperScope"; break; default: name = "Unknown"; break; } diff --git a/general.h b/general.h index 389d344550..8d5eb6b320 100644 --- a/general.h +++ b/general.h @@ -449,6 +449,9 @@ struct global struct retro_subsystem_info *special; unsigned num_special; + + struct retro_controller_info *ports; + unsigned num_ports; } system; struct diff --git a/libretro.h b/libretro.h index 4ef9d93a5c..4c962a6e9f 100755 --- a/libretro.h +++ b/libretro.h @@ -46,8 +46,22 @@ extern "C" { // It is not incremented for compatible changes to the API. #define RETRO_API_VERSION 1 -// Libretro's fundamental device abstractions. -#define RETRO_DEVICE_MASK 0xff +// +// Libretros fundamental device abstractions. +///////// +// +// Libretros input system consists of some standardized device types such as a joypad (with/without analog), +// mouse, keyboard, lightgun and a pointer. The functionality of these devices are fixed, and individual cores map +// their own concept of a controller to libretros abstractions. +// This makes it possible for frontends to map the abstract types to a real input device, +// and not having to worry about binding input correctly to arbitrary controller layouts. + + +#define RETRO_DEVICE_TYPE_SHIFT 8 +#define RETRO_DEVICE_MASK ((1 << RETRO_DEVICE_TYPE_SHIFT) - 1) +#define RETRO_DEVICE_SUBCLASS(base, id) (((id + 1) << RETRO_DEVICE_TYPE_SHIFT) | base) + +// Input disabled. #define RETRO_DEVICE_NONE 0 // The JOYPAD is called RetroPad. It is essentially a Super Nintendo controller, @@ -62,6 +76,7 @@ extern "C" { // KEYBOARD device lets one poll for raw key pressed. // It is poll based, so input callback will return with the current pressed state. +// For event/text based keyboard input, see RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. #define RETRO_DEVICE_KEYBOARD 3 // Lightgun X/Y coordinates are reported relatively to last poll, similar to mouse. @@ -87,7 +102,7 @@ extern "C" { // // To check if the pointer coordinates are valid (e.g. a touch display actually being touched), // PRESSED returns 1 or 0. -// If using a mouse, PRESSED will usually correspond to the left mouse button. +// If using a mouse on a desktop, PRESSED will usually correspond to the left mouse button, but this is a frontend decision. // PRESSED will only return 1 if the pointer is inside the game screen. // // For multi-touch, the index variable can be used to successively query more presses. @@ -96,16 +111,6 @@ extern "C" { // Eventually _PRESSED will return false for an index. No further presses are registered at this point. #define RETRO_DEVICE_POINTER 6 -// These device types are specializations of the base types above. -// They should only be used in retro_set_controller_type() to inform libretro implementations -// about use of a very specific device type. -// -// In input state callback, however, only the base type should be used in the 'device' field. -#define RETRO_DEVICE_JOYPAD_MULTITAP ((1 << 8) | RETRO_DEVICE_JOYPAD) -#define RETRO_DEVICE_LIGHTGUN_SUPER_SCOPE ((1 << 8) | RETRO_DEVICE_LIGHTGUN) -#define RETRO_DEVICE_LIGHTGUN_JUSTIFIER ((2 << 8) | RETRO_DEVICE_LIGHTGUN) -#define RETRO_DEVICE_LIGHTGUN_JUSTIFIERS ((3 << 8) | RETRO_DEVICE_LIGHTGUN) - // Buttons for the RetroPad (JOYPAD). // The placement of these is equivalent to placements on the Super Nintendo controller. // L2/R2/L3/R3 buttons correspond to the PS1 DualShock. @@ -611,7 +616,47 @@ enum retro_mod // and this environment call allows a libretro core to expose which subsystems are supported for use with retro_load_game_special(). // A core passes an array of retro_game_special_info which is terminated with a zeroed out retro_game_special_info struct. // - // If a core wants to use this functionality, SET_SPECIAL_GAME_TYPES **MUST** be called from within retro_set_environment(). + // If a core wants to use this functionality, SET_SUBSYSTEM_INFO **MUST** be called from within retro_set_environment(). + // +#define RETRO_ENVIRONMENT_SET_CONTROLLER_INFO 35 + // const struct retro_controller_info * -- + // This environment call lets a libretro core tell the frontend which + // controller types are recognized in calls to retro_set_controller_port_device(). + // + // Some emulators such as Super Nintendo + // support multiple lightgun types which must be specifically selected from. + // It is therefore sometimes necessary for a frontend to be able to tell + // the core about a special kind of input device which is not covered by the + // libretro input API. + // + // In order for a frontend to understand the workings of an input device, + // it must be a specialized type + // of the generic device types already defined in the libretro API. + // + // Which devices are supported can vary per input port. + // The core must pass an array of const struct retro_controller_info which is terminated with + // a blanked out struct. Each element of the struct corresponds to an ascending port index to retro_set_controller_port_device(). + // Even if special device types are set in the libretro core, libretro should only poll input based on the base input device types. + +struct retro_controller_description +{ + // Human-readable description of the controller. Even if using a generic input device type, this can be + // set to the particular device type the core uses. + const char *desc; + + // A computer-friendly short string identifier ([a-z]). + const char *ident; + + // Device type passed to retro_set_controller_port_device(). If the device type is a sub-class of a generic input device type, + // use the RETRO_DEVICE_SUBCLASS macro to create an ID. E.g. RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 1). + unsigned id; +}; + +struct retro_controller_info +{ + const struct retro_controller_description *types; + unsigned num_types; +}; struct retro_subsystem_memory_info { @@ -1208,6 +1253,8 @@ void retro_get_system_info(struct retro_system_info *info); void retro_get_system_av_info(struct retro_system_av_info *info); // Sets device to be used for player 'port'. +// By default, RETRO_DEVICE_JOYPAD is assumed to be plugged into all available ports. +// Setting a particular device type is not a guarantee that libretro cores will only poll input based on that particular device type. It is only a hint to the libretro core when a core cannot automatically detect the appropriate input device type on its own. It is also relevant when a core can change its behavior depending on device type. void retro_set_controller_port_device(unsigned port, unsigned device); // Resets the current game. diff --git a/retroarch.c b/retroarch.c index 34d195c615..cf46dbb177 100644 --- a/retroarch.c +++ b/retroarch.c @@ -783,11 +783,9 @@ static void print_help(void) printf("\t-N/--nodevice: Disconnects controller device connected to port (1 to %d).\n", MAX_PLAYERS); printf("\t-A/--dualanalog: Connect a DualAnalog controller to port (1 to %d).\n", MAX_PLAYERS); - printf("\t-m/--mouse: Connect a mouse into port of the device (1 to %d).\n", MAX_PLAYERS); - puts("\t-p/--scope: Connect a virtual SuperScope into port 2. (SNES specific)."); - puts("\t-j/--justifier: Connect a virtual Konami Justifier into port 2. (SNES specific)."); - puts("\t-J/--justifiers: Daisy chain two virtual Konami Justifiers into port 2. (SNES specific)."); - puts("\t-4/--multitap: Connect a SNES multitap to port 2. (SNES specific)."); + printf("\t-m/--mouse: Connect a mouse into controller port (1 to %d).\n", MAX_PLAYERS); + printf("\t-d/--device: Connect a generic device into port of the device (1 to %d).\n", MAX_PLAYERS); + puts("\t\tFormat is port:ID, where ID is an unsigned number corresponding to the particular device.\n"); #ifdef HAVE_BSV_MOVIE puts("\t-P/--bsvplay: Playback a BSV movie file."); @@ -933,12 +931,9 @@ static void parse_input(int argc, char *argv[]) { "appendconfig", 1, &val, 'C' }, { "mouse", 1, NULL, 'm' }, { "nodevice", 1, NULL, 'N' }, - { "scope", 0, NULL, 'p' }, - { "justifier", 0, NULL, 'j' }, - { "justifiers", 0, NULL, 'J' }, { "dualanalog", 1, NULL, 'A' }, + { "device", 1, NULL, 'd' }, { "savestate", 1, NULL, 'S' }, - { "multitap", 0, NULL, '4' }, #ifdef HAVE_BSV_MOVIE { "bsvplay", 1, NULL, 'P' }, { "bsvrecord", 1, NULL, 'R' }, @@ -989,7 +984,7 @@ static void parse_input(int argc, char *argv[]) #define BSV_MOVIE_ARG #endif - const char *optstring = "hs:fvS:m:p4jJA:c:U:DN:" BSV_MOVIE_ARG NETPLAY_ARG DYNAMIC_ARG FFMPEG_RECORD_ARG; + const char *optstring = "hs:fvS:m:A:c:U:DN:d:" BSV_MOVIE_ARG NETPLAY_ARG DYNAMIC_ARG FFMPEG_RECORD_ARG; for (;;) { @@ -1006,25 +1001,28 @@ static void parse_input(int argc, char *argv[]) print_help(); exit(0); - case '4': - g_settings.input.libretro_device[1] = RETRO_DEVICE_JOYPAD_MULTITAP; - g_extern.has_set_libretro_device[1] = true; - break; - - case 'j': - g_settings.input.libretro_device[1] = RETRO_DEVICE_LIGHTGUN_JUSTIFIER; - g_extern.has_set_libretro_device[1] = true; - break; - - case 'J': - g_settings.input.libretro_device[1] = RETRO_DEVICE_LIGHTGUN_JUSTIFIERS; - g_extern.has_set_libretro_device[1] = true; - break; - case 'Z': strlcpy(g_extern.subsystem, optarg, sizeof(g_extern.subsystem)); break; + case 'd': + { + struct string_list *list = string_split(optarg, ":"); + port = (list && list->size == 2) ? strtol(list->elems[0].data, NULL, 0) : 0; + unsigned id = (list && list->size == 2) ? strtoul(list->elems[1].data, NULL, 0) : 0; + string_list_free(list); + + if (port < 1 || port > MAX_PLAYERS) + { + RARCH_ERR("Connect device to a valid port.\n"); + print_help(); + rarch_fail(1, "parse_input()"); + } + g_settings.input.libretro_device[port - 1] = id; + g_extern.has_set_libretro_device[port - 1] = true; + break; + } + case 'A': port = strtol(optarg, NULL, 0); if (port < 1 || port > MAX_PLAYERS) @@ -1079,11 +1077,6 @@ static void parse_input(int argc, char *argv[]) g_extern.has_set_libretro_device[port - 1] = true; break; - case 'p': - g_settings.input.libretro_device[1] = RETRO_DEVICE_LIGHTGUN_SUPER_SCOPE; - g_extern.has_set_libretro_device[1] = true; - break; - case 'c': strlcpy(g_extern.config_path, optarg, sizeof(g_extern.config_path)); break; @@ -1274,39 +1267,26 @@ static void init_controllers(void) pretro_set_controller_port_device(i, device); - switch (device) + const struct retro_controller_description *desc = NULL; + if (i < g_extern.system.num_ports) + desc = libretro_find_controller_description(&g_extern.system.ports[i], device); + + const char *ident = desc ? desc->desc : NULL; + + if (!ident) { - case RETRO_DEVICE_NONE: - RARCH_LOG("Disconnecting device from port %u.\n", i + 1); - break; - - case RETRO_DEVICE_ANALOG: - RARCH_LOG("Connecting dualanalog to port %u.\n", i + 1); - break; - - case RETRO_DEVICE_MOUSE: - RARCH_LOG("Connecting mouse to port %u.\n", i + 1); - break; - - case RETRO_DEVICE_LIGHTGUN_JUSTIFIER: - RARCH_LOG("Connecting Justifier to port %u.\n", i + 1); - break; - - case RETRO_DEVICE_LIGHTGUN_JUSTIFIERS: - RARCH_LOG("Connecting Justifiers to port %u.\n", i + 1); - break; - - case RETRO_DEVICE_JOYPAD_MULTITAP: - RARCH_LOG("Connecting Multitap to port %u.\n", i + 1); - break; - - case RETRO_DEVICE_LIGHTGUN_SUPER_SCOPE: - RARCH_LOG("Connecting scope to port %u.\n", i + 1); - break; - - default: - break; + switch (device) + { + case RETRO_DEVICE_MOUSE: ident = "mouse"; break; + case RETRO_DEVICE_ANALOG: ident = "analog"; break; + default: ident = "Unknown"; break; + } } + + if (device == RETRO_DEVICE_NONE) + RARCH_LOG("Disconnecting device from port %u.\n", i + 1); + else + RARCH_LOG("Connecting %s to port %u.\n", ident, i + 1); } }