Merge pull request #646 from libretro/controller-info

Controller info libretro API
This commit is contained in:
Twinaphex 2014-04-12 17:18:07 +02:00
commit 54961c0dbf
8 changed files with 245 additions and 192 deletions

View File

@ -123,41 +123,22 @@ If a ROM is skipped, use a blank ("") command line argument.
ROMs must be loaded in an order which depends on the particular subsystem used.
See verbose log output to learn how a particular subsystem wants ROMs to be loaded.
.TP
\fB--mouse PORT, -m PORT\fR
Connects a mouse into port number PORT. Possible values for PORT are 1 to 8.
If set explicitly here, overrides config file for that port.
.TP
\fB--nodevice PORT, -N PORT\fR
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.

View File

@ -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 (ID: %u)\n", info[i].types[j].desc, 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:

View File

@ -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);

View File

@ -929,23 +929,30 @@ int menu_set_settings(void *data, unsigned setting, unsigned action)
break;
case RGUI_SETTINGS_BIND_DEVICE_TYPE:
{
static const unsigned device_types[] = {
RETRO_DEVICE_NONE,
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;
unsigned types = 0;
unsigned devices[128];
devices[types++] = RETRO_DEVICE_NONE;
devices[types++] = RETRO_DEVICE_JOYPAD;
devices[types++] = RETRO_DEVICE_ANALOG;
const struct retro_controller_info *desc = port < g_extern.system.num_ports ? &g_extern.system.ports[port] : NULL;
if (desc)
{
for (i = 0; i < desc->num_types; i++)
{
unsigned id = desc->types[i].id;
if (types < ARRAY_SIZE(devices) && id != RETRO_DEVICE_NONE && id != RETRO_DEVICE_JOYPAD && id != RETRO_DEVICE_ANALOG)
devices[types++] = id;
}
}
current_device = g_settings.input.libretro_device[port];
current_index = 0;
for (i = 0; i < ARRAY_SIZE(device_types); i++)
for (i = 0; i < types; i++)
{
if (current_device == device_types[i])
if (current_device == devices[i])
{
current_index = i;
break;
@ -960,12 +967,12 @@ int menu_set_settings(void *data, unsigned setting, unsigned action)
break;
case RGUI_ACTION_LEFT:
current_device = device_types[(current_index + ARRAY_SIZE(device_types) - 1) % ARRAY_SIZE(device_types)];
current_device = devices[(current_index + types - 1) % types];
break;
case RGUI_ACTION_RIGHT:
case RGUI_ACTION_OK:
current_device = device_types[(current_index + 1) % ARRAY_SIZE(device_types)];
current_device = devices[(current_index + 1) % types];
break;
default:
@ -2205,18 +2212,23 @@ void menu_set_settings_label(char *type_str, size_t type_str_size, unsigned *w,
}
case RGUI_SETTINGS_BIND_DEVICE_TYPE:
{
const char *name;
switch (g_settings.input.libretro_device[rgui->current_pad])
const struct retro_controller_description *desc = NULL;
if (rgui->current_pad < g_extern.system.num_ports)
{
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;
desc = libretro_find_controller_description(&g_extern.system.ports[rgui->current_pad],
g_settings.input.libretro_device[rgui->current_pad]);
}
const char *name = desc ? desc->desc : NULL;
if (!name) // Find generic name.
{
switch (g_settings.input.libretro_device[rgui->current_pad])
{
case RETRO_DEVICE_NONE: name = "None"; break;
case RETRO_DEVICE_JOYPAD: name = "Joypad"; break;
case RETRO_DEVICE_ANALOG: name = "Joypad w/ Analog"; break;
default: name = "Unknown"; break;
}
}
strlcpy(type_str, name, type_str_size);

View File

@ -449,6 +449,9 @@ struct global
struct retro_subsystem_info *special;
unsigned num_special;
struct retro_controller_info *ports;
unsigned num_ports;
} system;
struct

View File

@ -39,8 +39,7 @@ unsigned retro_api_version(void)
void retro_set_controller_port_device(unsigned port, unsigned device)
{
(void)port;
(void)device;
logging.log(RETRO_LOG_INFO, "Plugging device %u into port %u.\n", device, port);
}
void retro_get_system_info(struct retro_system_info *info)
@ -130,6 +129,19 @@ void retro_set_environment(retro_environment_t cb)
};
cb(RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO, (void*)types);
static const struct retro_controller_description controllers[] = {
{ "Dummy Controller #1", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) },
{ "Dummy Controller #2", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 1) },
{ "Augmented Joypad", RETRO_DEVICE_JOYPAD }, // Test overriding generic description in UI.
};
static const struct retro_controller_info ports[] = {
{ controllers, 3 },
{ NULL, 0 },
};
cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
}
void retro_set_audio_sample(retro_audio_sample_t cb)

View File

@ -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,44 @@ 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;
// 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 +1250,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.

View File

@ -783,11 +783,8 @@ 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-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.");
@ -931,14 +928,10 @@ static void parse_input(int argc, char *argv[])
{ "verbose", 0, NULL, 'v' },
{ "config", 1, NULL, 'c' },
{ "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 +982,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:A:c:U:DN:d:" BSV_MOVIE_ARG NETPLAY_ARG DYNAMIC_ARG FFMPEG_RECORD_ARG;
for (;;)
{
@ -1006,25 +999,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)
@ -1055,18 +1051,6 @@ static void parse_input(int argc, char *argv[])
g_extern.verbose = true;
break;
case 'm':
port = strtol(optarg, NULL, 0);
if (port < 1 || port > MAX_PLAYERS)
{
RARCH_ERR("Connect mouse to a valid port.\n");
print_help();
rarch_fail(1, "parse_input()");
}
g_settings.input.libretro_device[port - 1] = RETRO_DEVICE_MOUSE;
g_extern.has_set_libretro_device[port - 1] = true;
break;
case 'N':
port = strtol(optarg, NULL, 0);
if (port < 1 || port > MAX_PLAYERS)
@ -1079,11 +1063,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 +1253,25 @@ 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_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 (ID: %u) to port %u.\n", ident, device, i + 1);
}
}