mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-23 16:09:47 +00:00
347ea8157c
Prevent double free and null dereference when the controller is quickly reconnected. Handle error when controller device query returns null instead of crashing.
935 lines
30 KiB
C
935 lines
30 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2011-2017 - Daniel De Matteis
|
|
* Copyright (C) 2016-2019 - Brad Parker
|
|
* Copyright (C) 2016-2019 - Andrés Suárez
|
|
*
|
|
* 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/>.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
|
|
#include <compat/strl.h>
|
|
#include <file/file_path.h>
|
|
#include <string/stdstring.h>
|
|
#include <file/config_file.h>
|
|
|
|
#include "../configuration.h"
|
|
#include "../file_path_special.h"
|
|
#include "../list_special.h"
|
|
#include "../verbosity.h"
|
|
#include "../input/input_driver.h"
|
|
#include "../input/input_remapping.h"
|
|
|
|
#include "tasks_internal.h"
|
|
#ifdef HAVE_BLISSBOX
|
|
#include "../input/include/blissbox.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_MENU
|
|
#include "../menu/menu_driver.h"
|
|
#endif
|
|
|
|
enum autoconfig_handle_flags
|
|
{
|
|
AUTOCONF_FLAG_AUTOCONFIG_ENABLED = (1 << 0),
|
|
AUTOCONF_FLAG_SUPPRESS_NOTIFICATIONS = (1 << 1)
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
char *dir_autoconfig;
|
|
char *dir_driver_autoconfig;
|
|
config_file_t *autoconfig_file;
|
|
unsigned port;
|
|
input_device_info_t device_info; /* unsigned alignment */
|
|
uint8_t flags;
|
|
} autoconfig_handle_t;
|
|
|
|
/*********************/
|
|
/* Utility functions */
|
|
/*********************/
|
|
|
|
static void free_autoconfig_handle(autoconfig_handle_t *autoconfig_handle)
|
|
{
|
|
if (!autoconfig_handle)
|
|
return;
|
|
|
|
if (autoconfig_handle->dir_autoconfig)
|
|
{
|
|
free(autoconfig_handle->dir_autoconfig);
|
|
autoconfig_handle->dir_autoconfig = NULL;
|
|
}
|
|
|
|
if (autoconfig_handle->dir_driver_autoconfig)
|
|
{
|
|
free(autoconfig_handle->dir_driver_autoconfig);
|
|
autoconfig_handle->dir_driver_autoconfig = NULL;
|
|
}
|
|
|
|
if (autoconfig_handle->autoconfig_file)
|
|
{
|
|
config_file_free(autoconfig_handle->autoconfig_file);
|
|
autoconfig_handle->autoconfig_file = NULL;
|
|
}
|
|
|
|
free(autoconfig_handle);
|
|
autoconfig_handle = NULL;
|
|
}
|
|
|
|
static void input_autoconfigure_free(retro_task_t *task)
|
|
{
|
|
autoconfig_handle_t *autoconfig_handle = NULL;
|
|
if (task && (autoconfig_handle = (autoconfig_handle_t*)task->state))
|
|
free_autoconfig_handle(autoconfig_handle);
|
|
}
|
|
|
|
/******************************/
|
|
/* Autoconfig 'File' Handling */
|
|
/******************************/
|
|
|
|
/* Returns a value corresponding to the
|
|
* 'affinity' between the connected input
|
|
* device and the specified config file
|
|
* > 0: No match
|
|
* > 2: Device name matches
|
|
* > 3: VID+PID match
|
|
* > 5: Both device name and VID+PID match */
|
|
static unsigned input_autoconfigure_get_config_file_affinity(
|
|
autoconfig_handle_t *autoconfig_handle,
|
|
config_file_t *config)
|
|
{
|
|
int tmp_int = 0;
|
|
uint16_t config_vid = 0;
|
|
uint16_t config_pid = 0;
|
|
bool pid_match = false;
|
|
unsigned affinity = 0;
|
|
struct config_entry_list
|
|
*entry = NULL;
|
|
|
|
/* Parse config file */
|
|
if (config_get_int(config, "input_vendor_id", &tmp_int))
|
|
config_vid = (uint16_t)tmp_int;
|
|
|
|
if (config_get_int(config, "input_product_id", &tmp_int))
|
|
config_pid = (uint16_t)tmp_int;
|
|
|
|
/* > Bliss-Box shenanigans... */
|
|
#ifdef HAVE_BLISSBOX
|
|
if (autoconfig_handle->device_info.vid == BLISSBOX_VID)
|
|
config_pid = BLISSBOX_PID;
|
|
#endif
|
|
|
|
/* Check for matching VID+PID */
|
|
pid_match = (autoconfig_handle->device_info.vid == config_vid) &&
|
|
(autoconfig_handle->device_info.pid == config_pid) &&
|
|
(autoconfig_handle->device_info.vid != 0) &&
|
|
(autoconfig_handle->device_info.pid != 0);
|
|
|
|
/* > More Bliss-Box shenanigans... */
|
|
#ifdef HAVE_BLISSBOX
|
|
pid_match = pid_match &&
|
|
(autoconfig_handle->device_info.vid != BLISSBOX_VID) &&
|
|
(autoconfig_handle->device_info.pid != BLISSBOX_PID);
|
|
#endif
|
|
|
|
if (pid_match)
|
|
affinity += 3;
|
|
|
|
/* Check for matching device name */
|
|
if ( (entry = config_get_entry(config, "input_device"))
|
|
&& !string_is_empty(entry->value)
|
|
&& string_is_equal(entry->value,
|
|
autoconfig_handle->device_info.name))
|
|
affinity += 2;
|
|
|
|
return affinity;
|
|
}
|
|
|
|
/* 'Attaches' specified autoconfig file to autoconfig
|
|
* handle, parsing required device info metadata */
|
|
static void input_autoconfigure_set_config_file(
|
|
autoconfig_handle_t *autoconfig_handle,
|
|
config_file_t *config)
|
|
{
|
|
struct config_entry_list *entry = NULL;
|
|
|
|
/* Attach config file */
|
|
autoconfig_handle->autoconfig_file = config;
|
|
|
|
/* > Extract config file path + name */
|
|
if (!string_is_empty(config->path))
|
|
{
|
|
const char *config_file_name = path_basename_nocompression(config->path);
|
|
|
|
strlcpy(autoconfig_handle->device_info.config_path,
|
|
config->path,
|
|
sizeof(autoconfig_handle->device_info.config_path));
|
|
|
|
if (!string_is_empty(config_file_name))
|
|
strlcpy(autoconfig_handle->device_info.config_name,
|
|
config_file_name,
|
|
sizeof(autoconfig_handle->device_info.config_name));
|
|
}
|
|
|
|
/* Read device display name */
|
|
if ( (entry = config_get_entry(config, "input_device_display_name"))
|
|
&& !string_is_empty(entry->value))
|
|
strlcpy(autoconfig_handle->device_info.display_name,
|
|
entry->value,
|
|
sizeof(autoconfig_handle->device_info.display_name));
|
|
|
|
/* Set auto-configured status to 'true' */
|
|
autoconfig_handle->device_info.autoconfigured = true;
|
|
}
|
|
|
|
/* Attempts to find an 'external' autoconfig file
|
|
* (in the autoconfig directory) matching the connected
|
|
* input device
|
|
* > Returns 'true' if successful */
|
|
static bool input_autoconfigure_scan_config_files_external(
|
|
autoconfig_handle_t *autoconfig_handle)
|
|
{
|
|
size_t i;
|
|
const char *dir_autoconfig = autoconfig_handle->dir_autoconfig;
|
|
const char *dir_driver_autoconfig = autoconfig_handle->dir_driver_autoconfig;
|
|
struct string_list *config_file_list = NULL;
|
|
config_file_t *best_config = NULL;
|
|
unsigned max_affinity = 0;
|
|
bool match_found = false;
|
|
|
|
/* Attempt to fetch file listing from driver-specific
|
|
* autoconfig directory */
|
|
if (!string_is_empty(dir_driver_autoconfig) &&
|
|
path_is_directory(dir_driver_autoconfig))
|
|
config_file_list = dir_list_new_special(
|
|
dir_driver_autoconfig, DIR_LIST_AUTOCONFIG,
|
|
"cfg", false);
|
|
|
|
if (!config_file_list || (config_file_list->size < 1))
|
|
{
|
|
/* No files found - attempt to fetch listing
|
|
* from autoconfig base directory */
|
|
if (config_file_list)
|
|
{
|
|
string_list_free(config_file_list);
|
|
config_file_list = NULL;
|
|
}
|
|
|
|
if (!string_is_empty(dir_autoconfig) &&
|
|
path_is_directory(dir_autoconfig))
|
|
config_file_list = dir_list_new_special(
|
|
dir_autoconfig, DIR_LIST_AUTOCONFIG,
|
|
"cfg", false);
|
|
}
|
|
|
|
if (!config_file_list || (config_file_list->size < 1))
|
|
goto end;
|
|
|
|
/* Loop through external config files */
|
|
for (i = 0; i < config_file_list->size; i++)
|
|
{
|
|
const char *config_file_path = config_file_list->elems[i].data;
|
|
config_file_t *config = NULL;
|
|
unsigned affinity = 0;
|
|
|
|
if (string_is_empty(config_file_path))
|
|
continue;
|
|
|
|
/* Load autoconfig file */
|
|
if (!(config = config_file_new_from_path_to_string(config_file_path)))
|
|
continue;
|
|
|
|
/* Check for a match */
|
|
if (autoconfig_handle && config)
|
|
affinity = input_autoconfigure_get_config_file_affinity(
|
|
autoconfig_handle, config);
|
|
|
|
if (affinity > max_affinity)
|
|
{
|
|
if (best_config)
|
|
{
|
|
config_file_free(best_config);
|
|
best_config = NULL;
|
|
}
|
|
|
|
/* 'Cache' config file for later processing */
|
|
best_config = config;
|
|
config = NULL;
|
|
max_affinity = affinity;
|
|
|
|
/* An affinity of 5 is a 'perfect' match,
|
|
* and means we can return immediately */
|
|
if (affinity == 5)
|
|
break;
|
|
}
|
|
/* No match - just clean up config file */
|
|
else
|
|
{
|
|
config_file_free(config);
|
|
config = NULL;
|
|
}
|
|
}
|
|
|
|
/* If we reach this point and a config file has
|
|
* been cached, then we have a match */
|
|
if (best_config)
|
|
{
|
|
if (autoconfig_handle && best_config)
|
|
input_autoconfigure_set_config_file(
|
|
autoconfig_handle, best_config);
|
|
match_found = true;
|
|
}
|
|
|
|
end:
|
|
if (config_file_list)
|
|
{
|
|
string_list_free(config_file_list);
|
|
config_file_list = NULL;
|
|
}
|
|
|
|
return match_found;
|
|
}
|
|
|
|
/* Attempts to find an internal autoconfig definition
|
|
* matching the connected input device
|
|
* > Returns 'true' if successful */
|
|
static bool input_autoconfigure_scan_config_files_internal(
|
|
autoconfig_handle_t *autoconfig_handle)
|
|
{
|
|
size_t i;
|
|
|
|
/* Loop through internal autoconfig files
|
|
* > input_builtin_autoconfs is a static const,
|
|
* and may be read safely in any thread */
|
|
for (i = 0; input_builtin_autoconfs[i]; i++)
|
|
{
|
|
char *autoconfig_str = NULL;
|
|
config_file_t *config = NULL;
|
|
unsigned affinity = 0;
|
|
|
|
if (string_is_empty(input_builtin_autoconfs[i]))
|
|
continue;
|
|
|
|
/* Load autoconfig string */
|
|
autoconfig_str = strdup(input_builtin_autoconfs[i]);
|
|
config = config_file_new_from_string(
|
|
autoconfig_str, NULL);
|
|
|
|
/* > String no longer required - clean up */
|
|
free(autoconfig_str);
|
|
autoconfig_str = NULL;
|
|
|
|
/* Check for a match */
|
|
if (autoconfig_handle && config)
|
|
affinity = input_autoconfigure_get_config_file_affinity(
|
|
autoconfig_handle, config);
|
|
|
|
/* > In the case of internal autoconfigs, any kind
|
|
* of match is considered to be a success */
|
|
if (affinity > 0)
|
|
{
|
|
if (autoconfig_handle && config)
|
|
input_autoconfigure_set_config_file(
|
|
autoconfig_handle, config);
|
|
return true;
|
|
}
|
|
|
|
/* No match - clean up */
|
|
if (config)
|
|
{
|
|
config_file_free(config);
|
|
config = NULL;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*************************/
|
|
/* Autoconfigure Connect */
|
|
/*************************/
|
|
|
|
static void cb_input_autoconfigure_connect(
|
|
retro_task_t *task, void *task_data,
|
|
void *user_data, const char *err)
|
|
{
|
|
autoconfig_handle_t *autoconfig_handle = NULL;
|
|
unsigned port;
|
|
|
|
if (!task)
|
|
return;
|
|
|
|
if (!(autoconfig_handle = (autoconfig_handle_t*)task->state))
|
|
return;
|
|
|
|
/* Use local copy of port index for brevity... */
|
|
port = autoconfig_handle->port;
|
|
|
|
/* We perform the actual 'connect' in this
|
|
* callback, to ensure it occurs on the main
|
|
* thread */
|
|
|
|
/* Copy task handle parameters into global
|
|
* state objects:
|
|
* > Name */
|
|
if (!string_is_empty(autoconfig_handle->device_info.name))
|
|
input_config_set_device_name(port,
|
|
autoconfig_handle->device_info.name);
|
|
else
|
|
input_config_clear_device_name(port);
|
|
|
|
/* > Display name */
|
|
if (!string_is_empty(autoconfig_handle->device_info.display_name))
|
|
input_config_set_device_display_name(port,
|
|
autoconfig_handle->device_info.display_name);
|
|
else if (!string_is_empty(autoconfig_handle->device_info.name))
|
|
input_config_set_device_display_name(port,
|
|
autoconfig_handle->device_info.name);
|
|
else
|
|
input_config_clear_device_display_name(port);
|
|
|
|
/* > Driver */
|
|
if (!string_is_empty(autoconfig_handle->device_info.joypad_driver))
|
|
input_config_set_device_joypad_driver(port,
|
|
autoconfig_handle->device_info.joypad_driver);
|
|
else
|
|
input_config_clear_device_joypad_driver(port);
|
|
|
|
/* > VID/PID */
|
|
input_config_set_device_vid(port, autoconfig_handle->device_info.vid);
|
|
input_config_set_device_pid(port, autoconfig_handle->device_info.pid);
|
|
|
|
/* > Config file path/name */
|
|
if (!string_is_empty(autoconfig_handle->device_info.config_path))
|
|
input_config_set_device_config_path(port,
|
|
autoconfig_handle->device_info.config_path);
|
|
else
|
|
input_config_set_device_config_path(port,
|
|
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE));
|
|
|
|
if (!string_is_empty(autoconfig_handle->device_info.config_name))
|
|
input_config_set_device_config_name(port,
|
|
autoconfig_handle->device_info.config_name);
|
|
else
|
|
input_config_set_device_config_name(port,
|
|
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE));
|
|
|
|
/* > Auto-configured state */
|
|
input_config_set_device_autoconfigured(port,
|
|
autoconfig_handle->device_info.autoconfigured);
|
|
|
|
/* Reset any existing binds */
|
|
input_config_reset_autoconfig_binds(port);
|
|
|
|
/* If an autoconfig file is available, load its
|
|
* bind mappings */
|
|
if (autoconfig_handle->device_info.autoconfigured)
|
|
input_config_set_autoconfig_binds(port,
|
|
autoconfig_handle->autoconfig_file);
|
|
}
|
|
|
|
static void input_autoconfigure_connect_handler(retro_task_t *task)
|
|
{
|
|
autoconfig_handle_t *autoconfig_handle = NULL;
|
|
bool match_found = false;
|
|
const char *device_display_name = NULL;
|
|
char task_title[NAME_MAX_LENGTH + 16];
|
|
|
|
task_title[0] = '\0';
|
|
|
|
if (!task)
|
|
goto task_finished;
|
|
|
|
autoconfig_handle = (autoconfig_handle_t*)task->state;
|
|
|
|
if ( !autoconfig_handle
|
|
|| string_is_empty(autoconfig_handle->device_info.name)
|
|
|| !(autoconfig_handle->flags & AUTOCONF_FLAG_AUTOCONFIG_ENABLED))
|
|
goto task_finished;
|
|
|
|
/* Annoyingly, we have to scan all the autoconfig
|
|
* files (and in-built configs) in a single shot
|
|
* > Would prefer to scan one config per iteration
|
|
* of the task, but this would render the gamepad
|
|
* unusable for multiple frames after loading
|
|
* content... */
|
|
|
|
/* Scan in order of preference:
|
|
* - External autoconfig files
|
|
* - Internal autoconfig definitions */
|
|
if (!(match_found = input_autoconfigure_scan_config_files_external(
|
|
autoconfig_handle)))
|
|
match_found = input_autoconfigure_scan_config_files_internal(
|
|
autoconfig_handle);
|
|
|
|
/* If no match was found, attempt to use
|
|
* fallback mapping
|
|
* > Only enabled for certain drivers */
|
|
if (!match_found)
|
|
{
|
|
const char *fallback_device_name = NULL;
|
|
|
|
/* Preset fallback device names - must match
|
|
* those set in 'input_autodetect_builtin.c' */
|
|
if (string_is_equal(autoconfig_handle->device_info.joypad_driver,
|
|
"android"))
|
|
fallback_device_name = "Android Gamepad";
|
|
else if (string_is_equal(autoconfig_handle->device_info.joypad_driver,
|
|
"xinput"))
|
|
fallback_device_name = "XInput Controller";
|
|
else if (string_is_equal(autoconfig_handle->device_info.joypad_driver,
|
|
"sdl2"))
|
|
fallback_device_name = "Standard Gamepad";
|
|
|
|
if (!string_is_empty(fallback_device_name) &&
|
|
!string_is_equal(autoconfig_handle->device_info.name,
|
|
fallback_device_name))
|
|
{
|
|
char *name_backup = strdup(autoconfig_handle->device_info.name);
|
|
|
|
strlcpy(autoconfig_handle->device_info.name,
|
|
fallback_device_name,
|
|
sizeof(autoconfig_handle->device_info.name));
|
|
|
|
/* This is not a genuine match - leave
|
|
* match_found set to 'false' regardless
|
|
* of the outcome */
|
|
input_autoconfigure_scan_config_files_internal(
|
|
autoconfig_handle);
|
|
|
|
strlcpy(autoconfig_handle->device_info.name,
|
|
name_backup,
|
|
sizeof(autoconfig_handle->device_info.name));
|
|
|
|
free(name_backup);
|
|
name_backup = NULL;
|
|
}
|
|
}
|
|
|
|
/* Get display name for task status message */
|
|
device_display_name = autoconfig_handle->device_info.display_name;
|
|
if (string_is_empty(device_display_name))
|
|
device_display_name = autoconfig_handle->device_info.name;
|
|
if (string_is_empty(device_display_name))
|
|
device_display_name = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE);
|
|
|
|
/* Generate task status message
|
|
* > Note that 'connection successful' messages
|
|
* may be suppressed, but error messages are
|
|
* always shown */
|
|
if (autoconfig_handle->device_info.autoconfigured)
|
|
{
|
|
if (match_found)
|
|
{
|
|
/* A valid autoconfig was applied */
|
|
if (!(autoconfig_handle->flags & AUTOCONF_FLAG_SUPPRESS_NOTIFICATIONS))
|
|
{
|
|
snprintf(task_title, sizeof(task_title),
|
|
msg_hash_to_str(MSG_DEVICE_CONFIGURED_IN_PORT_NR),
|
|
device_display_name,
|
|
autoconfig_handle->port + 1);
|
|
}
|
|
}
|
|
/* Device is autoconfigured, but a (most likely
|
|
* incorrect) fallback definition was used... */
|
|
else
|
|
{
|
|
snprintf(task_title, sizeof(task_title),
|
|
msg_hash_to_str(MSG_DEVICE_NOT_CONFIGURED_FALLBACK_NR),
|
|
device_display_name,
|
|
autoconfig_handle->device_info.vid,
|
|
autoconfig_handle->device_info.pid);
|
|
}
|
|
}
|
|
/* Autoconfig failed */
|
|
else
|
|
{
|
|
snprintf(task_title, sizeof(task_title),
|
|
msg_hash_to_str(MSG_DEVICE_NOT_CONFIGURED_NR),
|
|
device_display_name,
|
|
autoconfig_handle->device_info.vid,
|
|
autoconfig_handle->device_info.pid);
|
|
}
|
|
|
|
/* Update task title */
|
|
task_free_title(task);
|
|
if (!string_is_empty(task_title))
|
|
{
|
|
task_set_title(task, strdup(task_title));
|
|
RARCH_LOG("[Autoconf]: %s.\n", task_title);
|
|
}
|
|
|
|
task_finished:
|
|
|
|
if (task)
|
|
task_set_finished(task, true);
|
|
}
|
|
|
|
static bool autoconfigure_connect_finder(retro_task_t *task, void *user_data)
|
|
{
|
|
autoconfig_handle_t *autoconfig_handle = NULL;
|
|
unsigned *port = NULL;
|
|
|
|
if (!task || !user_data)
|
|
return false;
|
|
|
|
if (task->handler != input_autoconfigure_connect_handler)
|
|
return false;
|
|
|
|
autoconfig_handle = (autoconfig_handle_t*)task->state;
|
|
if (!autoconfig_handle)
|
|
return false;
|
|
|
|
port = (unsigned*)user_data;
|
|
return (*port == autoconfig_handle->port);
|
|
}
|
|
|
|
bool input_autoconfigure_connect(
|
|
const char *name,
|
|
const char *display_name,
|
|
const char *driver,
|
|
unsigned port,
|
|
unsigned vid,
|
|
unsigned pid)
|
|
{
|
|
retro_task_t *task = NULL;
|
|
autoconfig_handle_t *autoconfig_handle = NULL;
|
|
bool driver_valid = false;
|
|
settings_t *settings = config_get_ptr();
|
|
bool autoconfig_enabled = settings ?
|
|
settings->bools.input_autodetect_enable : false;
|
|
const char *dir_autoconfig = settings ?
|
|
settings->paths.directory_autoconfig : NULL;
|
|
bool notification_show_autoconfig = settings ?
|
|
settings->bools.notification_show_autoconfig : true;
|
|
task_finder_data_t find_data;
|
|
|
|
if (port >= MAX_INPUT_DEVICES)
|
|
goto error;
|
|
|
|
/* Cannot connect a device that is currently
|
|
* being connected */
|
|
find_data.func = autoconfigure_connect_finder;
|
|
find_data.userdata = (void*)&port;
|
|
|
|
if (task_queue_find(&find_data))
|
|
goto error;
|
|
|
|
/* Configure handle */
|
|
if (!(autoconfig_handle = (autoconfig_handle_t*)
|
|
malloc(sizeof(autoconfig_handle_t))))
|
|
goto error;
|
|
|
|
autoconfig_handle->port = port;
|
|
autoconfig_handle->device_info.vid = vid;
|
|
autoconfig_handle->device_info.pid = pid;
|
|
autoconfig_handle->device_info.name[0] = '\0';
|
|
autoconfig_handle->device_info.display_name[0] = '\0';
|
|
autoconfig_handle->device_info.config_path[0] = '\0';
|
|
autoconfig_handle->device_info.config_name[0] = '\0';
|
|
autoconfig_handle->device_info.joypad_driver[0] = '\0';
|
|
autoconfig_handle->device_info.autoconfigured = false;
|
|
autoconfig_handle->device_info.name_index = 0;
|
|
if (autoconfig_enabled)
|
|
autoconfig_handle->flags |= AUTOCONF_FLAG_AUTOCONFIG_ENABLED;
|
|
if (!notification_show_autoconfig)
|
|
autoconfig_handle->flags |= AUTOCONF_FLAG_SUPPRESS_NOTIFICATIONS;
|
|
autoconfig_handle->dir_autoconfig = NULL;
|
|
autoconfig_handle->dir_driver_autoconfig = NULL;
|
|
autoconfig_handle->autoconfig_file = NULL;
|
|
|
|
if (!string_is_empty(name))
|
|
strlcpy(autoconfig_handle->device_info.name, name,
|
|
sizeof(autoconfig_handle->device_info.name));
|
|
|
|
if (!string_is_empty(display_name))
|
|
strlcpy(autoconfig_handle->device_info.display_name, display_name,
|
|
sizeof(autoconfig_handle->device_info.display_name));
|
|
|
|
if ((driver_valid = !string_is_empty(driver)))
|
|
strlcpy(autoconfig_handle->device_info.joypad_driver,
|
|
driver, sizeof(autoconfig_handle->device_info.joypad_driver));
|
|
|
|
/* > Have to cache both the base autoconfig directory
|
|
* and the driver-specific autoconfig directory
|
|
* - Driver-specific directory is scanned by
|
|
* default, if available
|
|
* - If driver-specific directory is unavailable,
|
|
* we scan the base autoconfig directory as
|
|
* a fallback */
|
|
if (!string_is_empty(dir_autoconfig))
|
|
{
|
|
autoconfig_handle->dir_autoconfig = strdup(dir_autoconfig);
|
|
|
|
if (driver_valid)
|
|
{
|
|
char dir_driver_autoconfig[PATH_MAX_LENGTH];
|
|
/* Generate driver-specific autoconfig directory */
|
|
fill_pathname_join_special(dir_driver_autoconfig,
|
|
dir_autoconfig,
|
|
autoconfig_handle->device_info.joypad_driver,
|
|
sizeof(dir_driver_autoconfig));
|
|
|
|
if (!string_is_empty(dir_driver_autoconfig))
|
|
autoconfig_handle->dir_driver_autoconfig =
|
|
strdup(dir_driver_autoconfig);
|
|
}
|
|
}
|
|
|
|
/* Bliss-Box shenanigans... */
|
|
#ifdef HAVE_BLISSBOX
|
|
if (autoconfig_handle->device_info.vid == BLISSBOX_VID)
|
|
input_autoconfigure_blissbox_override_handler(
|
|
(int)autoconfig_handle->device_info.vid,
|
|
(int)autoconfig_handle->device_info.pid,
|
|
autoconfig_handle->device_info.name,
|
|
sizeof(autoconfig_handle->device_info.name));
|
|
#endif
|
|
|
|
/* If we are reconnecting a device that is already
|
|
* connected and autoconfigured, then there is no need
|
|
* to generate additional 'connection successful'
|
|
* task status messages
|
|
* > Can skip this check if autoconfig notifications
|
|
* have been disabled by the user */
|
|
if ( !(autoconfig_handle->flags & AUTOCONF_FLAG_SUPPRESS_NOTIFICATIONS)
|
|
&& !string_is_empty(autoconfig_handle->device_info.name))
|
|
{
|
|
const char *last_device_name = input_config_get_device_name(port);
|
|
uint16_t last_vid = input_config_get_device_vid(port);
|
|
uint16_t last_pid = input_config_get_device_pid(port);
|
|
bool last_autoconfigured = input_config_get_device_autoconfigured(port);
|
|
|
|
if (!string_is_empty(last_device_name) &&
|
|
string_is_equal(autoconfig_handle->device_info.name,
|
|
last_device_name) &&
|
|
(autoconfig_handle->device_info.vid == last_vid) &&
|
|
(autoconfig_handle->device_info.pid == last_pid) &&
|
|
last_autoconfigured)
|
|
autoconfig_handle->flags |= AUTOCONF_FLAG_SUPPRESS_NOTIFICATIONS;
|
|
}
|
|
|
|
/* Configure task */
|
|
task = task_init();
|
|
|
|
if (!task)
|
|
goto error;
|
|
|
|
task->handler = input_autoconfigure_connect_handler;
|
|
task->state = autoconfig_handle;
|
|
task->mute = false;
|
|
task->title = NULL;
|
|
task->callback = cb_input_autoconfigure_connect;
|
|
task->cleanup = input_autoconfigure_free;
|
|
|
|
task_queue_push(task);
|
|
|
|
return true;
|
|
|
|
error:
|
|
|
|
if (task)
|
|
{
|
|
free(task);
|
|
task = NULL;
|
|
}
|
|
|
|
free_autoconfig_handle(autoconfig_handle);
|
|
return false;
|
|
}
|
|
|
|
/****************************/
|
|
/* Autoconfigure Disconnect */
|
|
/****************************/
|
|
|
|
static void cb_input_autoconfigure_disconnect(
|
|
retro_task_t *task, void *task_data,
|
|
void *user_data, const char *err)
|
|
{
|
|
unsigned port;
|
|
autoconfig_handle_t *autoconfig_handle = NULL;
|
|
|
|
if (!task)
|
|
return;
|
|
|
|
if (!(autoconfig_handle = (autoconfig_handle_t*)task->state))
|
|
return;
|
|
|
|
/* Use local copy of port index for brevity... */
|
|
port = autoconfig_handle->port;
|
|
|
|
/* We perform the actual 'disconnect' in this
|
|
* callback, to ensure it occurs on the main thread */
|
|
input_config_clear_device_name(port);
|
|
input_config_clear_device_display_name(port);
|
|
input_config_clear_device_config_path(port);
|
|
input_config_clear_device_config_name(port);
|
|
input_config_clear_device_joypad_driver(port);
|
|
input_config_set_device_vid(port, 0);
|
|
input_config_set_device_pid(port, 0);
|
|
input_config_set_device_autoconfigured(port, false);
|
|
input_config_reset_autoconfig_binds(port);
|
|
}
|
|
|
|
static void input_autoconfigure_disconnect_handler(retro_task_t *task)
|
|
{
|
|
autoconfig_handle_t *autoconfig_handle = NULL;
|
|
const char *device_display_name = NULL;
|
|
char task_title[NAME_MAX_LENGTH + 16];
|
|
|
|
task_title[0] = '\0';
|
|
|
|
if (!task)
|
|
goto task_finished;
|
|
|
|
if (!(autoconfig_handle = (autoconfig_handle_t*)task->state))
|
|
goto task_finished;
|
|
|
|
/* Get display name for task status message */
|
|
device_display_name = autoconfig_handle->device_info.display_name;
|
|
if (string_is_empty(device_display_name))
|
|
device_display_name = autoconfig_handle->device_info.name;
|
|
if (string_is_empty(device_display_name))
|
|
device_display_name = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE);
|
|
|
|
/* Set task title */
|
|
snprintf(task_title, sizeof(task_title),
|
|
msg_hash_to_str(MSG_DEVICE_DISCONNECTED_FROM_PORT_NR),
|
|
device_display_name,
|
|
autoconfig_handle->port + 1);
|
|
|
|
task_free_title(task);
|
|
if (!(autoconfig_handle->flags & AUTOCONF_FLAG_SUPPRESS_NOTIFICATIONS))
|
|
task_set_title(task, strdup(task_title));
|
|
if (!string_is_empty(task_title))
|
|
RARCH_LOG("[Autoconf]: %s.\n", task_title);
|
|
|
|
task_finished:
|
|
|
|
if (task)
|
|
task_set_finished(task, true);
|
|
}
|
|
|
|
static bool autoconfigure_disconnect_finder(retro_task_t *task, void *user_data)
|
|
{
|
|
autoconfig_handle_t *autoconfig_handle = NULL;
|
|
unsigned *port = NULL;
|
|
|
|
if (!task || !user_data)
|
|
return false;
|
|
|
|
if (task->handler != input_autoconfigure_disconnect_handler)
|
|
return false;
|
|
|
|
if (!(autoconfig_handle = (autoconfig_handle_t*)task->state))
|
|
return false;
|
|
|
|
port = (unsigned*)user_data;
|
|
return (*port == autoconfig_handle->port);
|
|
}
|
|
|
|
/* Note: There is no real need for autoconfigure
|
|
* 'disconnect' to be a task - we are merely setting
|
|
* a handful of variables. However:
|
|
* - Making it a task means we can call
|
|
* input_autoconfigure_disconnect() on any thread
|
|
* thread, and defer the global state changes until
|
|
* the task queue is handled on the *main* thread
|
|
* - By using a task for both 'connect' and 'disconnect',
|
|
* we ensure uniformity of OSD status messages */
|
|
bool input_autoconfigure_disconnect(unsigned port, const char *name)
|
|
{
|
|
retro_task_t *task = NULL;
|
|
autoconfig_handle_t *autoconfig_handle = NULL;
|
|
task_finder_data_t find_data;
|
|
settings_t *settings = config_get_ptr();
|
|
input_driver_state_t *input_st = input_state_get_ptr();
|
|
bool notification_show_autoconfig = settings ? settings->bools.notification_show_autoconfig : true;
|
|
bool pause_on_disconnect = settings ? settings->bools.pause_on_disconnect : true;
|
|
bool core_is_running = runloop_state_get_ptr()->flags & RUNLOOP_FLAG_CORE_RUNNING;
|
|
|
|
if (port >= MAX_INPUT_DEVICES)
|
|
goto error;
|
|
|
|
/* Cannot disconnect a device that is currently
|
|
* being disconnected */
|
|
find_data.func = autoconfigure_disconnect_finder;
|
|
find_data.userdata = (void*)&port;
|
|
|
|
if (task_queue_find(&find_data))
|
|
goto error;
|
|
|
|
/* Configure handle */
|
|
autoconfig_handle = (autoconfig_handle_t*)calloc(1, sizeof(autoconfig_handle_t));
|
|
|
|
if (!autoconfig_handle)
|
|
goto error;
|
|
|
|
autoconfig_handle->port = port;
|
|
if (!notification_show_autoconfig)
|
|
autoconfig_handle->flags |= AUTOCONF_FLAG_SUPPRESS_NOTIFICATIONS;
|
|
|
|
/* Use display_name as name instead since autoconfig display_name
|
|
* is destroyed already, and real name does not matter at this point */
|
|
if (input_st && !string_is_empty(input_st->input_device_info[port].display_name))
|
|
strlcpy(autoconfig_handle->device_info.name,
|
|
input_st->input_device_info[port].display_name,
|
|
sizeof(autoconfig_handle->device_info.name));
|
|
else if (!string_is_empty(name))
|
|
strlcpy(autoconfig_handle->device_info.name,
|
|
name, sizeof(autoconfig_handle->device_info.name));
|
|
|
|
/* Configure task */
|
|
if (!(task = task_init()))
|
|
goto error;
|
|
|
|
task->handler = input_autoconfigure_disconnect_handler;
|
|
task->state = autoconfig_handle;
|
|
task->title = NULL;
|
|
task->callback = cb_input_autoconfigure_disconnect;
|
|
task->cleanup = input_autoconfigure_free;
|
|
|
|
task_queue_push(task);
|
|
|
|
if (pause_on_disconnect && core_is_running)
|
|
{
|
|
#ifdef HAVE_MENU
|
|
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
|
|
bool menu_is_alive = menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE;
|
|
|
|
if (menu_pause_libretro && !menu_is_alive)
|
|
command_event(CMD_EVENT_MENU_TOGGLE, NULL);
|
|
else if (!menu_pause_libretro)
|
|
command_event(CMD_EVENT_PAUSE, NULL);
|
|
#else
|
|
command_event(CMD_EVENT_PAUSE, NULL);
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
|
|
error:
|
|
|
|
if (task)
|
|
{
|
|
free(task);
|
|
task = NULL;
|
|
}
|
|
|
|
free_autoconfig_handle(autoconfig_handle);
|
|
|
|
return false;
|
|
}
|