Files
RetroArch/input/input_driver.c
T

8459 lines
278 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2016-2019 - Andr s Su rez (input mapper code)
*
* 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 Foundation, 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 "libretro.h"
#include <queues/message_queue.h>
#include <streams/interface_stream.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <string/stdstring.h>
#include <encodings/utf.h>
#include <clamping.h>
#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif /* HAVE_CONFIG_H */
#if defined(_WIN32) && !defined(SOCKET)
#include <winsock2.h>
#endif
#include "input_driver.h"
#include "input_keymaps.h"
#include "input_remapping.h"
#include "input_osk.h"
#include "input_types.h"
#ifdef HAVE_BSV_MOVIE
#include "bsv/bsvmovie.h"
#endif
#ifdef HAVE_CHEEVOS
#include "../cheevos/cheevos.h"
#endif
#ifdef HAVE_NETWORKING
#include <net/net_compat.h>
#include <net/net_socket.h>
#endif
#ifdef HAVE_MENU
#include "../menu/menu_driver.h"
#endif
#include "../accessibility.h"
#include "../command.h"
#include "../config.def.keybinds.h"
#include "../configuration.h"
#include "../core_info.h"
#include "../driver.h"
#include "../frontend/frontend_driver.h"
#include "../list_special.h"
#include "../paths.h"
#include "../performance_counters.h"
#include "../retroarch.h"
#include "../tasks/tasks_internal.h"
#include "../verbosity.h"
#ifdef ANDROID
#include "../frontend/drivers/platform_unix.h"
#endif
#include "../ai/game_ai.h"
#define HOLD_BTN_DELAY_SEC 2
/* Precomputed reciprocals used in analog input scaling.
* 0x8000 = 32768 is the full int16 range (unsigned half),
* 0x7fff = 32767 is the max positive int16 value. */
#define INV_0x8000 (1.0f / 0x8000)
#define INV_0x7fff (1.0f / 0x7fff)
/* Depends on ASCII character values */
#define ISPRINT(c) (((int)(c) >= ' ' && (int)(c) <= '~') ? 1 : 0)
#define IS_UTF8_CONTINUATION(c) ((((uint8_t)(c)) & 0xc0) == 0x80)
#define INPUT_REMOTE_KEY_PRESSED(input_st, key, port) (input_st->remote_st_ptr.buttons[(port)] & (UINT64_C(1) << (key)))
#define IS_COMPOSITION(c) ( (c & 0x0F000000) ? 1 : 0)
#define IS_COMPOSITION_KR(c) ( (c & 0x01000000) ? 1 : 0)
#define IS_END_COMPOSITION(c) ( (c & 0xF0000000) ? 1 : 0)
struct input_remote
{
#if defined(HAVE_NETWORKING) && defined(HAVE_NETWORKGAMEPAD)
#ifdef _WIN32
SOCKET net_fd[MAX_USERS];
#else
int net_fd[MAX_USERS];
#endif
#endif
bool state[RARCH_BIND_LIST_END];
};
/**
* check_input_driver_block_hotkey:
*
* Checks if 'hotkey enable' key is pressed.
*
* If we haven't bound anything to this,
* always allow hotkeys.
* If we hold ENABLE_HOTKEY button, block all libretro input to allow
* hotkeys to be bound to same keys as RetroPad.
**/
#define CHECK_INPUT_DRIVER_BLOCK_HOTKEY(normal_bind, autoconf_bind) \
( \
(((normal_bind)->key != RETROK_UNKNOWN) \
|| ((normal_bind)->mbutton != NO_BTN) \
|| ((normal_bind)->joykey != NO_BTN) \
|| ((normal_bind)->joyaxis != AXIS_NONE) \
|| ((autoconf_bind)->key != RETROK_UNKNOWN) \
|| ((autoconf_bind)->joykey != NO_BTN) \
|| ((autoconf_bind)->joyaxis != AXIS_NONE)) \
)
/* Human readable order of input binds */
const unsigned input_config_bind_order[24] = {
RETRO_DEVICE_ID_JOYPAD_UP,
RETRO_DEVICE_ID_JOYPAD_DOWN,
RETRO_DEVICE_ID_JOYPAD_LEFT,
RETRO_DEVICE_ID_JOYPAD_RIGHT,
RETRO_DEVICE_ID_JOYPAD_B,
RETRO_DEVICE_ID_JOYPAD_A,
RETRO_DEVICE_ID_JOYPAD_Y,
RETRO_DEVICE_ID_JOYPAD_X,
RETRO_DEVICE_ID_JOYPAD_SELECT,
RETRO_DEVICE_ID_JOYPAD_START,
RETRO_DEVICE_ID_JOYPAD_L,
RETRO_DEVICE_ID_JOYPAD_R,
RETRO_DEVICE_ID_JOYPAD_L2,
RETRO_DEVICE_ID_JOYPAD_R2,
RETRO_DEVICE_ID_JOYPAD_L3,
RETRO_DEVICE_ID_JOYPAD_R3,
19, /* Left Analog Up */
18, /* Left Analog Down */
17, /* Left Analog Left */
16, /* Left Analog Right */
23, /* Right Analog Up */
22, /* Right Analog Down */
21, /* Right Analog Left */
20, /* Right Analog Right */
};
/**************************************/
/* TODO/FIXME - turn these into static global variable */
retro_keybind_set input_config_binds[MAX_USERS];
retro_keybind_set input_autoconf_binds[MAX_USERS];
uint64_t lifecycle_state = 0;
static void *input_null_init(const char *joypad_driver) { return (void*)-1; }
static void input_null_poll(void *data) { }
static int16_t input_null_input_state(
void *data,
const input_device_driver_t *joypad,
const input_device_driver_t *sec_joypad,
rarch_joypad_info_t *joypad_info,
const retro_keybind_set *retro_keybinds,
bool keyboard_mapping_blocked,
unsigned port, unsigned device, unsigned index, unsigned id) { return 0; }
static void input_null_free(void *data) { }
static bool input_null_set_sensor_state(void *data, unsigned port,
enum retro_sensor_action action, unsigned rate) { return false; }
static float input_null_get_sensor_input(void *data, unsigned port, unsigned id) { return 0.0; }
static uint64_t input_null_get_capabilities(void *data) { return 0; }
static void input_null_grab_mouse(void *data, bool state) { }
static bool input_null_grab_stdin(void *data) { return false; }
static void input_null_keypress_vibrate(void) { }
static input_driver_t input_null = {
input_null_init,
input_null_poll,
input_null_input_state,
input_null_free,
input_null_set_sensor_state,
input_null_get_sensor_input,
input_null_get_capabilities,
"null",
input_null_grab_mouse,
input_null_grab_stdin,
input_null_keypress_vibrate
};
static input_device_driver_t null_joypad = {
NULL, /* init */
NULL, /* query_pad */
NULL, /* destroy */
NULL, /* button */
NULL, /* state */
NULL, /* get_buttons */
NULL, /* axis */
NULL, /* poll */
NULL, /* rumble */
NULL, /* rumble_gain */
NULL, /* set_sensor_state */
NULL, /* get_sensor_input */
NULL, /* name */
"null",
};
#ifdef HAVE_HID
static bool null_hid_joypad_query(void *data, unsigned pad) {
return pad < MAX_USERS; }
static const char *null_hid_joypad_name(
void *data, unsigned pad) { return NULL; }
static void null_hid_joypad_get_buttons(void *data,
unsigned port, input_bits_t *state) { BIT256_CLEAR_ALL_PTR(state); }
static int16_t null_hid_joypad_button(
void *data, unsigned port, uint16_t joykey) { return 0; }
static bool null_hid_joypad_rumble(void *data, unsigned pad,
enum retro_rumble_effect effect, uint16_t strength) { return false; }
static int16_t null_hid_joypad_axis(
void *data, unsigned port, uint32_t joyaxis) { return 0; }
static void *null_hid_init(void) { return (void*)-1; }
static void null_hid_free(const void *data) { }
static void null_hid_poll(void *data) { }
static int16_t null_hid_joypad_state(
void *data,
rarch_joypad_info_t *joypad_info,
const void *binds_data,
unsigned port) { return 0; }
static hid_driver_t null_hid = {
null_hid_init, /* init */
null_hid_joypad_query, /* joypad_query */
null_hid_free, /* free */
null_hid_joypad_button, /* button */
null_hid_joypad_state, /* state */
null_hid_joypad_get_buttons, /* get_buttons */
null_hid_joypad_axis, /* axis */
null_hid_poll, /* poll */
null_hid_joypad_rumble, /* rumble */
null_hid_joypad_name, /* joypad_name */
"null",
};
#endif
input_device_driver_t *joypad_drivers[] = {
#ifdef HAVE_XINPUT
&xinput_joypad,
#endif
#ifdef GEKKO
&gx_joypad,
#endif
#ifdef WIIU
&wiiu_joypad,
#endif
#ifdef _XBOX1
&xdk_joypad,
#endif
#if defined(ORBIS)
&ps4_joypad,
#endif
#if defined(__PSL1GHT__) || defined(__PS3__)
&ps3_joypad,
#endif
#if defined(PSP) || defined(VITA)
&psp_joypad,
#endif
#if defined(PS2)
&ps2_joypad,
#endif
#ifdef _3DS
&ctr_joypad,
#endif
#ifdef SWITCH
&switch_joypad,
#endif
#ifdef HAVE_DINPUT
&dinput_joypad,
#endif
#ifdef HAVE_UDEV
&udev_joypad,
#endif
#if defined(__linux) && !defined(ANDROID)
&linuxraw_joypad,
#endif
#ifdef HAVE_PARPORT
&parport_joypad,
#endif
#ifdef ANDROID
&android_joypad,
#endif
#if defined(HAVE_SDL3) || defined(HAVE_SDL) || defined(HAVE_SDL2)
&sdl_joypad,
#endif
#if defined(DINGUX) && defined(HAVE_SDL_DINGUX)
&sdl_dingux_joypad,
#endif
#ifdef __QNX__
&qnx_joypad,
#endif
#ifdef HAVE_MFI
&mfi_joypad,
#endif
#ifdef DJGPP
&dos_joypad,
#endif
/* Selecting the HID gamepad driver disables the Wii U gamepad. So while
* we want the HID code to be compiled & linked, we don't want the driver
* to be selectable in the UI. */
#if defined(HAVE_HID) && !defined(WIIU)
&hid_joypad,
#endif
#ifdef EMSCRIPTEN
&rwebpad_joypad,
#endif
#if defined(_WIN32) && !defined(_XBOX) && _WIN32_WINNT >= 0x0501 && !defined(__WINRT__)
#ifdef HAVE_WINRAWINPUT
&winraw_joypad,
#endif
#endif
#ifdef HAVE_TEST_DRIVERS
&test_joypad,
#endif
&null_joypad,
NULL,
};
input_driver_t *input_drivers[] = {
#ifdef ORBIS
&input_ps4,
#endif
#if defined(__PSL1GHT__) || defined(__PS3__)
&input_ps3,
#endif
#if defined(SN_TARGET_PSP2) || defined(PSP) || defined(VITA)
&input_psp,
#endif
#if defined(PS2)
&input_ps2,
#endif
#if defined(_3DS)
&input_ctr,
#endif
#if defined(SWITCH)
&input_switch,
#endif
#ifdef HAVE_X11
&input_x,
#endif
#ifdef HAVE_WAYLAND
&input_wayland,
#endif
#ifdef __WINRT__
&input_uwp,
#endif
#ifdef XENON
&input_xenon360,
#endif
#if defined(_WIN32) && !defined(_XBOX) && _WIN32_WINNT >= 0x0501 && !defined(__WINRT__)
#ifdef HAVE_WINRAWINPUT
/* winraw only available since XP */
&input_winraw,
#endif
#endif
#if defined(HAVE_XINPUT2) || defined(HAVE_XINPUT_XBOX1) || defined(__WINRT__)
&input_xinput,
#endif
#ifdef HAVE_DINPUT
&input_dinput,
#endif
#if (defined(HAVE_SDL) || defined(HAVE_SDL2)) && !(defined(HAVE_COCOA) || defined(HAVE_COCOA_METAL))
&input_sdl,
#endif
#if defined(DINGUX) && defined(HAVE_SDL_DINGUX)
&input_sdl_dingux,
#endif
#ifdef GEKKO
&input_gx,
#endif
#ifdef WIIU
&input_wiiu,
#endif
#ifdef ANDROID
&input_android,
#endif
#ifdef HAVE_UDEV
&input_udev,
#endif
#if defined(__linux__) && !defined(ANDROID)
&input_linuxraw,
#endif
#if defined(HAVE_COCOA) || defined(HAVE_COCOATOUCH) || defined(HAVE_COCOA_METAL)
&input_cocoa,
#endif
#ifdef __QNX__
&input_qnx,
#endif
#ifdef EMSCRIPTEN
&input_rwebinput,
#endif
#ifdef DJGPP
&input_dos,
#endif
#ifdef HAVE_TEST_DRIVERS
&input_test,
#endif
&input_null,
NULL,
};
#ifdef HAVE_HID
hid_driver_t *hid_drivers[] = {
#if defined(HAVE_BTSTACK)
&btstack_hid,
#endif
#if defined(__APPLE__) && defined(HAVE_IOHIDMANAGER)
&iohidmanager_hid,
#endif
#if defined(HAVE_LIBUSB) && defined(HAVE_THREADS)
&libusb_hid,
#endif
#ifdef HW_RVL
&wiiusb_hid,
#endif
#if defined(WIIU)
&wiiu_hid,
#endif
&null_hid,
NULL,
};
#endif
static input_driver_state_t input_driver_st = {0}; /* double alignment */
/**************************************/
input_driver_state_t *input_state_get_ptr(void)
{
return &input_driver_st;
}
/**
* config_get_input_driver_options:
*
* Get an enumerated list of all input driver names, separated by '|'.
*
* Returns: string listing of all input driver names, separated by '|'.
**/
const char* config_get_input_driver_options(void)
{
return char_list_new_special(STRING_LIST_INPUT_DRIVERS, NULL);
}
/**
* config_get_joypad_driver_options:
*
* Get an enumerated list of all joypad driver names, separated by '|'.
*
* Returns: string listing of all joypad driver names, separated by '|'.
**/
const char* config_get_joypad_driver_options(void)
{
return char_list_new_special(STRING_LIST_INPUT_JOYPAD_DRIVERS, NULL);
}
/**
* Finds first suitable joypad driver and initializes. Used as a fallback by
* input_joypad_init_driver when no matching driver is found.
*
* @param data joypad state data pointer, which can be NULL and will be
* initialized by the new joypad driver, if one is found.
*
* @return joypad driver if found and initialized, otherwise NULL.
**/
static const input_device_driver_t *input_joypad_init_first(void *data)
{
int i;
for (i = 0; joypad_drivers[i]; i++)
{
if ( joypad_drivers[i]
&& joypad_drivers[i]->init)
{
void *ptr = joypad_drivers[i]->init(data);
if (ptr)
{
RARCH_LOG("[Input] Found joypad driver: \"%s\".\n",
joypad_drivers[i]->ident);
return joypad_drivers[i];
}
}
}
return NULL;
}
bool input_driver_set_rumble(
unsigned port, unsigned joy_idx,
enum retro_rumble_effect effect, uint16_t strength)
{
const input_device_driver_t *primary_joypad;
const input_device_driver_t *sec_joypad;
bool rumble_state = false;
if (joy_idx >= MAX_USERS)
return false;
primary_joypad = input_driver_st.primary_joypad;
sec_joypad = input_driver_st.secondary_joypad;
if (primary_joypad && primary_joypad->set_rumble)
rumble_state = primary_joypad->set_rumble(joy_idx, effect, strength);
/* if sec_joypad exists, this set_rumble() return value will replace primary_joypad's return */
if (sec_joypad && sec_joypad->set_rumble)
rumble_state = sec_joypad->set_rumble(joy_idx, effect, strength);
return rumble_state;
}
bool input_driver_set_rumble_gain(
unsigned gain,
unsigned input_max_users)
{
int i;
if ( input_driver_st.primary_joypad
&& input_driver_st.primary_joypad->set_rumble_gain)
{
for (i = 0; i < (int)input_max_users; i++)
input_driver_st.primary_joypad->set_rumble_gain(i, gain);
return true;
}
return false;
}
bool input_driver_set_sensor(
unsigned port, bool sensors_enable,
enum retro_sensor_action action, unsigned rate)
{
const input_driver_t *current_driver;
bool enabled = false;
if (!input_driver_st.current_data)
return false;
/* If sensors are disabled, inhibit any enable
* actions (but always allow disable actions) */
if (!sensors_enable
&& ((action == RETRO_SENSOR_ACCELEROMETER_ENABLE)
|| (action == RETRO_SENSOR_GYROSCOPE_ENABLE)
|| (action == RETRO_SENSOR_ILLUMINANCE_ENABLE)))
return false;
if (input_driver_st.primary_joypad && input_driver_st.primary_joypad->set_sensor_state)
enabled = input_driver_st.primary_joypad->set_sensor_state(port, action, rate);
if ( !enabled
&& (current_driver = input_driver_st.current_driver)
&& current_driver->set_sensor_state)
{
void *current_data = input_driver_st.current_data;
enabled |= current_driver->set_sensor_state(current_data,
port, action, rate);
}
return enabled;
}
/**************************************/
float input_driver_get_sensor(
unsigned port, bool sensors_enable, unsigned id)
{
if (!sensors_enable)
return 0.0f;
if (input_driver_st.primary_joypad && input_driver_st.primary_joypad->get_sensor_input)
{
float value;
/* if joypad driver's get_sensor_input returns false, let input driver try */
if (input_driver_st.primary_joypad->get_sensor_input(port, id, &value))
return value;
}
if (input_driver_st.current_data)
{
const input_driver_t *input = input_driver_st.current_driver;
if (input->get_sensor_input)
{
void *current_data = input_driver_st.current_data;
return input->get_sensor_input(current_data, port, id);
}
}
return 0.0f;
}
const input_device_driver_t *input_joypad_init_driver(
const char *ident, void *data)
{
if (ident && *ident)
{
int i;
for (i = 0; joypad_drivers[i]; i++)
{
if (string_is_equal(ident, joypad_drivers[i]->ident)
&& joypad_drivers[i]->init)
{
void *ptr = joypad_drivers[i]->init(data);
if (ptr)
{
RARCH_LOG("[Input] Found joypad driver: \"%s\".\n",
joypad_drivers[i]->ident);
return joypad_drivers[i];
}
}
}
}
/* Fall back to first available driver */
return input_joypad_init_first(data);
}
static bool input_driver_button_combo_hold(
unsigned mode,
unsigned button,
retro_time_t current_time,
input_bits_t *p_input)
{
rarch_timer_t *timer = &input_driver_st.combo_timers[mode];
runloop_state_t *runloop_st = runloop_state_get_ptr();
static bool enable_hotkey_dupe = false;
/* Flag current press when button and 'enable_hotkey' are the same,
* because 'input_hotkey_block_delay' clears the combo button bit. */
if ( BIT256_GET_PTR(p_input, RARCH_ENABLE_HOTKEY)
&& BIT256_GET_PTR(p_input, button))
enable_hotkey_dupe = true;
/* Ignore press if 'enable_hotkey' is not the combo button. */
if ( BIT256_GET_PTR(p_input, RARCH_ENABLE_HOTKEY)
&& !BIT256_GET_PTR(p_input, button)
&& !enable_hotkey_dupe)
return false;
/* Allow using the same button for 'enable_hotkey' if set,
* and stop timer if holding fast-forward or slow-motion */
if ( !BIT256_GET_PTR(p_input, button)
&& !( BIT256_GET_PTR(p_input, RARCH_ENABLE_HOTKEY)
&& !(input_driver_st.flags & INP_FLAG_BLOCK_HOTKEY)
&& !(runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION)
&& !(runloop_st->flags & RUNLOOP_FLAG_FASTMOTION))
)
{
/* Timer only runs while start is held down */
enable_hotkey_dupe = false;
timer->timer_begin = false;
timer->timer_end = true;
timer->timeout_end = 0;
return false;
}
/* User started holding down the start button, start the timer */
if (!timer->timer_begin)
{
timer->timeout_us = HOLD_BTN_DELAY_SEC * 1000000;
timer->timeout_end = current_time + timer->timeout_us;
timer->timer_begin = true;
timer->timer_end = false;
}
timer->current = current_time;
timer->timeout_us = (timer->timeout_end - timer->current);
if (!timer->timer_end && (timer->timeout_us <= 0))
{
/* Start has been held down long enough,
* stop timer and enter menu */
enable_hotkey_dupe = false;
timer->timer_begin = false;
timer->timer_end = true;
timer->timeout_end = 0;
return true;
}
return false;
}
bool input_driver_pointer_is_offscreen(int16_t x, int16_t y)
{
/* Use unsigned cast: values outside [-32700, 32700] will wrap
* beyond 32700*2 = 65400 when biased by 32700 */
return ((uint16_t)(x + 32700) > 65400u) || ((uint16_t)(y + 32700) > 65400u);
}
unsigned input_driver_lightgun_id_convert(unsigned id)
{
switch (id)
{
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT:
return RARCH_LIGHTGUN_DPAD_RIGHT;
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT:
return RARCH_LIGHTGUN_DPAD_LEFT;
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP:
return RARCH_LIGHTGUN_DPAD_UP;
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN:
return RARCH_LIGHTGUN_DPAD_DOWN;
case RETRO_DEVICE_ID_LIGHTGUN_SELECT:
return RARCH_LIGHTGUN_SELECT;
case RETRO_DEVICE_ID_LIGHTGUN_PAUSE:
return RARCH_LIGHTGUN_START;
case RETRO_DEVICE_ID_LIGHTGUN_RELOAD:
return RARCH_LIGHTGUN_RELOAD;
case RETRO_DEVICE_ID_LIGHTGUN_TRIGGER:
return RARCH_LIGHTGUN_TRIGGER;
case RETRO_DEVICE_ID_LIGHTGUN_AUX_A:
return RARCH_LIGHTGUN_AUX_A;
case RETRO_DEVICE_ID_LIGHTGUN_AUX_B:
return RARCH_LIGHTGUN_AUX_B;
case RETRO_DEVICE_ID_LIGHTGUN_AUX_C:
return RARCH_LIGHTGUN_AUX_C;
case RETRO_DEVICE_ID_LIGHTGUN_START:
return RARCH_LIGHTGUN_START;
default:
break;
}
return 0;
}
bool input_driver_button_combo(
unsigned mode,
retro_time_t current_time,
input_bits_t *p_input)
{
switch (mode)
{
case INPUT_COMBO_DOWN_Y_L_R:
if ( BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_DOWN)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_Y)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_L)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_R))
return true;
break;
case INPUT_COMBO_L3_R3:
if ( BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_L3)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_R3))
return true;
break;
case INPUT_COMBO_L1_R1_START_SELECT:
if ( BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_L)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_R)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_START)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_SELECT))
return true;
break;
case INPUT_COMBO_START_SELECT:
if ( BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_START)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_SELECT))
return true;
break;
case INPUT_COMBO_L3_R:
if ( BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_L3)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_R))
return true;
break;
case INPUT_COMBO_L_R:
if ( BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_L)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_R))
return true;
break;
case INPUT_COMBO_DOWN_SELECT:
if ( BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_DOWN)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_SELECT))
return true;
break;
case INPUT_COMBO_L2_R2:
if ( BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_L2)
&& BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_R2))
return true;
break;
case INPUT_COMBO_HOLD_START:
return input_driver_button_combo_hold(
INPUT_COMBO_HOLD_START, RETRO_DEVICE_ID_JOYPAD_START, current_time, p_input);
case INPUT_COMBO_HOLD_SELECT:
return input_driver_button_combo_hold(
INPUT_COMBO_HOLD_SELECT, RETRO_DEVICE_ID_JOYPAD_SELECT, current_time, p_input);
default:
case INPUT_COMBO_NONE:
break;
}
return false;
}
static int32_t input_state_wrap(
input_driver_t *input,
void *data,
const input_device_driver_t *joypad,
const input_device_driver_t *sec_joypad,
rarch_joypad_info_t *joypad_info,
const retro_keybind_set *binds,
bool keyboard_mapping_blocked,
unsigned _port,
unsigned device,
unsigned idx,
unsigned id)
{
int32_t ret = 0;
if (!binds)
return 0;
/* Do a bitwise OR to combine input states together */
if (device == RETRO_DEVICE_JOYPAD)
{
if (id == RETRO_DEVICE_ID_JOYPAD_MASK)
{
if (joypad)
ret |= joypad->state(joypad_info, binds[_port], _port);
if (sec_joypad)
ret |= sec_joypad->state(joypad_info, binds[_port], _port);
}
else if (id < RARCH_FIRST_CUSTOM_BIND)
{
/* Standard joypad buttons (0-15): serve from a per-port
* bitmask cache when available. The cache is populated
* either by an earlier JOYPAD_MASK query for this port
* (common in modern cores) or lazily on the first
* individual button query (old-style cores).
* This avoids up to 32 indirect joypad->button()/axis()
* calls per port per frame. */
input_driver_state_t *input_st = &input_driver_st;
if (_port < MAX_USERS
&& !input_st->joypad_state_cache_valid[_port])
{
int32_t cached = 0;
if (joypad)
cached |= joypad->state(joypad_info, binds[_port], _port);
if (sec_joypad)
cached |= sec_joypad->state(joypad_info, binds[_port], _port);
if (input && input->input_state)
cached |= input->input_state(
data, joypad, sec_joypad, joypad_info, binds,
keyboard_mapping_blocked,
_port, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_MASK);
input_st->joypad_state_cache[_port] = cached;
input_st->joypad_state_cache_valid[_port] = true;
}
if ( _port < MAX_USERS
&& (input_st->joypad_state_cache[_port] & (1 << id)))
return 1;
/* Cache says not pressed; skip input->input_state() below
* since the cache already incorporates it. */
return 0;
}
else
{
/* Extended bind IDs (turbo, hold, meta keys) are not
* covered by joypad->state(), so use the original
* per-button dispatch path. */
if (binds[_port][id].valid)
{
const uint64_t bind_joykey = binds[_port][id].joykey;
const uint64_t bind_joyaxis = binds[_port][id].joyaxis;
const uint64_t autobind_joykey = joypad_info->auto_binds[id].joykey;
const uint64_t autobind_joyaxis= joypad_info->auto_binds[id].joyaxis;
uint16_t port = joypad_info->joy_idx;
float axis_threshold = joypad_info->axis_threshold;
float inv_0x8000 = INV_0x8000;
const uint64_t joykey = (bind_joykey != NO_BTN)
? bind_joykey : autobind_joykey;
const uint64_t joyaxis = (bind_joyaxis != AXIS_NONE)
? bind_joyaxis : autobind_joyaxis;
if (joypad)
{
if ((uint16_t)joykey != NO_BTN && joypad->button(
port, (uint16_t)joykey))
return 1;
if (joyaxis != AXIS_NONE &&
((float)abs(joypad->axis(port, (uint32_t)joyaxis))
* inv_0x8000) > axis_threshold)
return 1;
}
if (sec_joypad)
{
if ((uint16_t)joykey != NO_BTN && sec_joypad->button(
port, (uint16_t)joykey))
return 1;
if (joyaxis != AXIS_NONE &&
((float)abs(sec_joypad->axis(port, (uint32_t)joyaxis))
* inv_0x8000) > axis_threshold)
return 1;
}
}
}
}
else if (device == RETRO_DEVICE_KEYBOARD)
{
/* Always ignore null key. */
if (id == RETROK_UNKNOWN)
return ret;
}
if (input && input->input_state)
ret |= input->input_state(
data,
joypad,
sec_joypad,
joypad_info,
binds,
keyboard_mapping_blocked,
_port,
device,
idx,
id);
/* Populate the per-port joypad cache from the MASK result so that
* subsequent individual button queries (from hybrid or old-style
* cores) can be served from cache without a second joypad->state()
* call. This is a no-op when the cache is already valid (i.e.
* individual queries ran before the first MASK query). */
if ( device == RETRO_DEVICE_JOYPAD
&& id == RETRO_DEVICE_ID_JOYPAD_MASK
&& _port < MAX_USERS)
{
input_driver_state_t *input_st = &input_driver_st;
if (!input_st->joypad_state_cache_valid[_port])
{
input_st->joypad_state_cache[_port] = ret;
input_st->joypad_state_cache_valid[_port] = true;
}
}
return ret;
}
static int16_t input_joypad_axis(
float input_analog_deadzone,
float input_analog_sensitivity,
const input_device_driver_t *drv,
unsigned port, uint32_t joyaxis, float normal_mag)
{
int16_t val = ((joyaxis != AXIS_NONE) && drv && drv->axis)
? drv->axis(port, joyaxis) : 0;
if (input_analog_deadzone)
{
/* If below deadzone, short-circuit immediately */
if (normal_mag <= input_analog_deadzone)
return 0;
/* Radial/linear scaled deadzone rescale.
* Precompute 1/normal_mag once; clamp implicitly via MIN/MAX. */
{
float inv_mag = (normal_mag > 1.0f) ? (1.0f / normal_mag) : 1.0f;
float dz_scale = (normal_mag - input_analog_deadzone)
/ (1.0f - input_analog_deadzone);
if (dz_scale > 1.0f) dz_scale = 1.0f;
val = (int16_t)((float)val * inv_mag * dz_scale);
}
}
if (input_analog_sensitivity != 1.0f)
{
/* Fused: scale = sensitivity / 0x7fff; new_val = val * scale * 0x7fff
* reduces to val * sensitivity, with clamp */
int new_val = (int)((float)val * input_analog_sensitivity);
if (new_val > 0x7fff)
return 0x7fff;
if (new_val < -0x7fff)
return -0x7fff;
return (int16_t)new_val;
}
return val;
}
/**
* input_joypad_analog_button:
* @drv : Input device driver handle.
* @port : User number.
* @idx : Analog key index.
* E.g.:
* - RETRO_DEVICE_INDEX_ANALOG_LEFT
* - RETRO_DEVICE_INDEX_ANALOG_RIGHT
* @ident : Analog key identifier.
* E.g.:
* - RETRO_DEVICE_ID_ANALOG_X
* - RETRO_DEVICE_ID_ANALOG_Y
* @binds : Binds of user.
*
* Gets analog value of analog key identifiers @idx and @ident
* from user with number @port with provided keybinds (@binds).
*
* Returns: analog value on success, otherwise 0.
**/
static int16_t input_joypad_analog_button(
float input_analog_deadzone,
float input_analog_sensitivity,
const input_device_driver_t *drv,
rarch_joypad_info_t *joypad_info,
unsigned ident,
const struct retro_keybind *bind)
{
int16_t res = 0;
float normal_mag = 0.0f;
uint16_t joy_idx = joypad_info->joy_idx;
uint32_t axis = (bind->joyaxis == AXIS_NONE)
? joypad_info->auto_binds[ident].joyaxis
: bind->joyaxis;
/* Early exit for digital-only buttons: if neither the user bind
* nor the autoconfig bind has an analog axis, this button has no
* analog capability. Skip the input_joypad_axis() call and
* deadzone magnitude computation entirely — go straight to the
* digital button fallback. Saves one drv->axis() indirect call
* plus float math per pressed digital button in the remap loop. */
if (axis == AXIS_NONE)
{
uint16_t key = (bind->joykey == NO_BTN)
? joypad_info->auto_binds[ident].joykey
: bind->joykey;
if (drv->button(joy_idx, key))
return 0x7fff;
return 0;
}
/* Analog button - call drv->axis at most once */
if (input_analog_deadzone)
{
int16_t mult = drv->axis(joy_idx, axis);
if (mult != 0)
{
/* Manual abs avoids fabs() float-to-int rounding ambiguity */
normal_mag = (float)(mult < 0 ? -mult : mult) * INV_0x7fff;
}
}
/* If the result is zero, it's got a digital button attached to it instead */
if ((res = abs(input_joypad_axis(input_analog_deadzone,
input_analog_sensitivity, drv,
joy_idx, axis, normal_mag))) == 0)
{
uint16_t key = (bind->joykey == NO_BTN)
? joypad_info->auto_binds[ident].joykey
: bind->joykey;
if (drv->button(joy_idx, key))
return 0x7fff;
return 0;
}
return res;
}
static int16_t input_joypad_analog_axis(
unsigned input_analog_dpad_mode,
float input_analog_deadzone,
float input_analog_sensitivity,
const input_device_driver_t *drv,
rarch_joypad_info_t *joypad_info,
unsigned idx,
unsigned ident,
const struct retro_keybind *binds)
{
int16_t res = 0;
/* Analog sticks. Either RETRO_DEVICE_INDEX_ANALOG_LEFT
* or RETRO_DEVICE_INDEX_ANALOG_RIGHT */
unsigned ident_minus = 0;
unsigned ident_plus = 0;
unsigned ident_x_minus = 0;
unsigned ident_x_plus = 0;
unsigned ident_y_minus = 0;
unsigned ident_y_plus = 0;
const struct retro_keybind *bind_minus = NULL;
const struct retro_keybind *bind_plus = NULL;
const struct retro_keybind *bind_x_minus = NULL;
const struct retro_keybind *bind_x_plus = NULL;
const struct retro_keybind *bind_y_minus = NULL;
const struct retro_keybind *bind_y_plus = NULL;
/* Skip analog input with analog_dpad_mode */
switch (input_analog_dpad_mode)
{
case ANALOG_DPAD_LSTICK:
if (idx == RETRO_DEVICE_INDEX_ANALOG_LEFT)
return 0;
break;
case ANALOG_DPAD_RSTICK:
if (idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
return 0;
break;
case ANALOG_DPAD_LRSTICK:
case ANALOG_DPAD_TWINSTICK:
if ( idx == RETRO_DEVICE_INDEX_ANALOG_LEFT
|| idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
return 0;
break;
default:
break;
}
input_conv_analog_id_to_bind_id(idx, ident, ident_minus, ident_plus);
bind_minus = &binds[ident_minus];
bind_plus = &binds[ident_plus];
if (!bind_minus->valid || !bind_plus->valid)
return 0;
input_conv_analog_id_to_bind_id(idx,
RETRO_DEVICE_ID_ANALOG_X, ident_x_minus, ident_x_plus);
bind_x_minus = &binds[ident_x_minus];
bind_x_plus = &binds[ident_x_plus];
if (!bind_x_minus->valid || !bind_x_plus->valid)
return 0;
input_conv_analog_id_to_bind_id(idx,
RETRO_DEVICE_ID_ANALOG_Y, ident_y_minus, ident_y_plus);
bind_y_minus = &binds[ident_y_minus];
bind_y_plus = &binds[ident_y_plus];
if (!bind_y_minus->valid || !bind_y_plus->valid)
return 0;
/* Keyboard bind priority */
if ( bind_plus->key != RETROK_UNKNOWN
|| bind_minus->key != RETROK_UNKNOWN)
{
input_driver_state_t *input_st = &input_driver_st;
if (bind_plus->key && input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
NULL,
joypad_info,
(*input_st->libretro_input_binds),
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
0, RETRO_DEVICE_KEYBOARD, 0,
bind_plus->key))
res = 0x7fff;
if (bind_minus->key && input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
NULL,
joypad_info,
(*input_st->libretro_input_binds),
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
0, RETRO_DEVICE_KEYBOARD, 0,
bind_minus->key))
res += -0x7fff;
if (res)
return res;
}
{
uint32_t axis_minus = (bind_minus->joyaxis == AXIS_NONE)
? joypad_info->auto_binds[ident_minus].joyaxis
: bind_minus->joyaxis;
uint32_t axis_plus = (bind_plus->joyaxis == AXIS_NONE)
? joypad_info->auto_binds[ident_plus].joyaxis
: bind_plus->joyaxis;
float normal_mag = 0.0f;
/* normalized magnitude of stick actuation, needed for scaled
* radial deadzone */
if (input_analog_deadzone)
{
float x = 0.0f;
float y = 0.0f;
float mag_sq;
uint32_t x_axis_minus = (bind_x_minus->joyaxis == AXIS_NONE)
? joypad_info->auto_binds[ident_x_minus].joyaxis
: bind_x_minus->joyaxis;
uint32_t x_axis_plus = (bind_x_plus->joyaxis == AXIS_NONE)
? joypad_info->auto_binds[ident_x_plus].joyaxis
: bind_x_plus->joyaxis;
uint32_t y_axis_minus = (bind_y_minus->joyaxis == AXIS_NONE)
? joypad_info->auto_binds[ident_y_minus].joyaxis
: bind_y_minus->joyaxis;
uint32_t y_axis_plus = (bind_y_plus->joyaxis == AXIS_NONE)
? joypad_info->auto_binds[ident_y_plus].joyaxis
: bind_y_plus->joyaxis;
/* normalized magnitude for radial scaled analog deadzone */
if (x_axis_plus != AXIS_NONE && drv->axis)
x = drv->axis(
joypad_info->joy_idx, x_axis_plus);
if (x_axis_minus != AXIS_NONE && drv->axis)
x += drv->axis(joypad_info->joy_idx,
x_axis_minus);
if (y_axis_plus != AXIS_NONE && drv->axis)
y = drv->axis(
joypad_info->joy_idx, y_axis_plus);
if (y_axis_minus != AXIS_NONE && drv->axis)
y += drv->axis(
joypad_info->joy_idx, y_axis_minus);
/* Use squared magnitude to avoid sqrtf when possible.
* dz_sq is the squared deadzone threshold in raw axis units.
* If mag_sq <= dz_sq, the stick is inside the deadzone
* and input_joypad_axis() will return 0 without needing
* the actual magnitude. Only compute sqrtf when we're in
* the rescaling region (between deadzone and full tilt). */
mag_sq = x * x + y * y;
{
float dz_raw = input_analog_deadzone * 0x7fff;
float dz_sq = dz_raw * dz_raw;
if (mag_sq <= dz_sq)
normal_mag = 0.0f; /* Will trigger early-exit in input_joypad_axis */
else
normal_mag = INV_0x7fff * sqrtf(mag_sq);
}
}
res = abs(
input_joypad_axis(
input_analog_deadzone,
input_analog_sensitivity,
drv, joypad_info->joy_idx,
axis_plus, normal_mag));
res -= abs(
input_joypad_axis(
input_analog_deadzone,
input_analog_sensitivity,
drv, joypad_info->joy_idx,
axis_minus, normal_mag));
}
if (res == 0)
{
uint16_t key_minus = (bind_minus->joykey == NO_BTN)
? joypad_info->auto_binds[ident_minus].joykey
: bind_minus->joykey;
uint16_t key_plus = (bind_plus->joykey == NO_BTN)
? joypad_info->auto_binds[ident_plus].joykey
: bind_plus->joykey;
if (drv->button && drv->button(joypad_info->joy_idx, key_plus))
res = 0x7fff;
if (drv->button && drv->button(joypad_info->joy_idx, key_minus))
res += -0x7fff;
}
return res;
}
/**
* input_joypad_analog_stick:
*
* Processes both X and Y axes of a single analog stick together,
* computing the radial deadzone magnitude only once. This avoids
* the redundant drv->axis() calls and sqrtf() that occur when
* input_joypad_analog_axis() is called separately for X and Y.
*
* @param input_analog_dpad_mode Analog-to-dpad mode for this port
* @param input_analog_deadzone Deadzone threshold (0.0 = disabled)
* @param input_analog_sensitivity Sensitivity multiplier
* @param drv Joypad driver
* @param joypad_info Joypad info (joy_idx, auto_binds, threshold)
* @param idx Stick index (ANALOG_LEFT or ANALOG_RIGHT)
* @param binds Keybinds for this port
* @param out_x Output: X axis value
* @param out_y Output: Y axis value
*
* @return true if the stick was processed, false if skipped (dpad mode)
*/
static bool input_joypad_analog_stick(
unsigned input_analog_dpad_mode,
float input_analog_deadzone,
float input_analog_sensitivity,
const input_device_driver_t *drv,
rarch_joypad_info_t *joypad_info,
unsigned idx,
const struct retro_keybind *binds,
int16_t *out_x, int16_t *out_y)
{
unsigned ident_x_minus = 0;
unsigned ident_x_plus = 0;
unsigned ident_y_minus = 0;
unsigned ident_y_plus = 0;
const struct retro_keybind *bind_x_minus = NULL;
const struct retro_keybind *bind_x_plus = NULL;
const struct retro_keybind *bind_y_minus = NULL;
const struct retro_keybind *bind_y_plus = NULL;
float normal_mag = 0.0f;
*out_x = 0;
*out_y = 0;
/* Skip analog input with analog_dpad_mode */
switch (input_analog_dpad_mode)
{
case ANALOG_DPAD_LSTICK:
if (idx == RETRO_DEVICE_INDEX_ANALOG_LEFT)
return false;
break;
case ANALOG_DPAD_RSTICK:
if (idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
return false;
break;
case ANALOG_DPAD_LRSTICK:
case ANALOG_DPAD_TWINSTICK:
if ( idx == RETRO_DEVICE_INDEX_ANALOG_LEFT
|| idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
return false;
break;
default:
break;
}
/* Resolve all 4 bind IDs for this stick at once */
input_conv_analog_id_to_bind_id(idx,
RETRO_DEVICE_ID_ANALOG_X, ident_x_minus, ident_x_plus);
input_conv_analog_id_to_bind_id(idx,
RETRO_DEVICE_ID_ANALOG_Y, ident_y_minus, ident_y_plus);
bind_x_minus = &binds[ident_x_minus];
bind_x_plus = &binds[ident_x_plus];
bind_y_minus = &binds[ident_y_minus];
bind_y_plus = &binds[ident_y_plus];
if ( !bind_x_minus->valid || !bind_x_plus->valid
|| !bind_y_minus->valid || !bind_y_plus->valid)
return false;
/* Keyboard bind priority — check X */
if ( bind_x_plus->key != RETROK_UNKNOWN
|| bind_x_minus->key != RETROK_UNKNOWN)
{
input_driver_state_t *input_st = &input_driver_st;
bool kb_blocked = !!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED);
if (bind_x_plus->key && input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
NULL, joypad_info,
(*input_st->libretro_input_binds),
kb_blocked,
0, RETRO_DEVICE_KEYBOARD, 0,
bind_x_plus->key))
*out_x = 0x7fff;
if (bind_x_minus->key && input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
NULL, joypad_info,
(*input_st->libretro_input_binds),
kb_blocked,
0, RETRO_DEVICE_KEYBOARD, 0,
bind_x_minus->key))
*out_x += -0x7fff;
}
/* Keyboard bind priority — check Y */
if ( bind_y_plus->key != RETROK_UNKNOWN
|| bind_y_minus->key != RETROK_UNKNOWN)
{
input_driver_state_t *input_st = &input_driver_st;
bool kb_blocked = !!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED);
if (bind_y_plus->key && input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
NULL, joypad_info,
(*input_st->libretro_input_binds),
kb_blocked,
0, RETRO_DEVICE_KEYBOARD, 0,
bind_y_plus->key))
*out_y = 0x7fff;
if (bind_y_minus->key && input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
NULL, joypad_info,
(*input_st->libretro_input_binds),
kb_blocked,
0, RETRO_DEVICE_KEYBOARD, 0,
bind_y_minus->key))
*out_y += -0x7fff;
}
/* If keyboard produced results for both axes, we're done */
if (*out_x && *out_y)
return true;
{
uint32_t x_axis_minus = (bind_x_minus->joyaxis == AXIS_NONE)
? joypad_info->auto_binds[ident_x_minus].joyaxis
: bind_x_minus->joyaxis;
uint32_t x_axis_plus = (bind_x_plus->joyaxis == AXIS_NONE)
? joypad_info->auto_binds[ident_x_plus].joyaxis
: bind_x_plus->joyaxis;
uint32_t y_axis_minus = (bind_y_minus->joyaxis == AXIS_NONE)
? joypad_info->auto_binds[ident_y_minus].joyaxis
: bind_y_minus->joyaxis;
uint32_t y_axis_plus = (bind_y_plus->joyaxis == AXIS_NONE)
? joypad_info->auto_binds[ident_y_plus].joyaxis
: bind_y_plus->joyaxis;
/* Compute radial magnitude ONCE for this stick */
if (input_analog_deadzone)
{
float x = 0.0f;
float y = 0.0f;
float mag_sq;
if (x_axis_plus != AXIS_NONE && drv->axis)
x = drv->axis(joypad_info->joy_idx, x_axis_plus);
if (x_axis_minus != AXIS_NONE && drv->axis)
x += drv->axis(joypad_info->joy_idx, x_axis_minus);
if (y_axis_plus != AXIS_NONE && drv->axis)
y = drv->axis(joypad_info->joy_idx, y_axis_plus);
if (y_axis_minus != AXIS_NONE && drv->axis)
y += drv->axis(joypad_info->joy_idx, y_axis_minus);
mag_sq = x * x + y * y;
{
float dz_raw = input_analog_deadzone * 0x7fff;
float dz_sq = dz_raw * dz_raw;
if (mag_sq <= dz_sq)
normal_mag = 0.0f;
else
normal_mag = INV_0x7fff * sqrtf(mag_sq);
}
}
/* X axis — reuse cached normal_mag */
if (!*out_x)
{
int16_t x_val;
x_val = abs(input_joypad_axis(
input_analog_deadzone, input_analog_sensitivity,
drv, joypad_info->joy_idx,
x_axis_plus, normal_mag));
x_val -= abs(input_joypad_axis(
input_analog_deadzone, input_analog_sensitivity,
drv, joypad_info->joy_idx,
x_axis_minus, normal_mag));
if (x_val == 0)
{
uint16_t key_minus = (bind_x_minus->joykey == NO_BTN)
? joypad_info->auto_binds[ident_x_minus].joykey
: bind_x_minus->joykey;
uint16_t key_plus = (bind_x_plus->joykey == NO_BTN)
? joypad_info->auto_binds[ident_x_plus].joykey
: bind_x_plus->joykey;
if (drv->button && drv->button(joypad_info->joy_idx, key_plus))
x_val = 0x7fff;
if (drv->button && drv->button(joypad_info->joy_idx, key_minus))
x_val += -0x7fff;
}
*out_x = x_val;
}
/* Y axis — reuse same cached normal_mag */
if (!*out_y)
{
int16_t y_val;
y_val = abs(input_joypad_axis(
input_analog_deadzone, input_analog_sensitivity,
drv, joypad_info->joy_idx,
y_axis_plus, normal_mag));
y_val -= abs(input_joypad_axis(
input_analog_deadzone, input_analog_sensitivity,
drv, joypad_info->joy_idx,
y_axis_minus, normal_mag));
if (y_val == 0)
{
uint16_t key_minus = (bind_y_minus->joykey == NO_BTN)
? joypad_info->auto_binds[ident_y_minus].joykey
: bind_y_minus->joykey;
uint16_t key_plus = (bind_y_plus->joykey == NO_BTN)
? joypad_info->auto_binds[ident_y_plus].joykey
: bind_y_plus->joykey;
if (drv->button && drv->button(joypad_info->joy_idx, key_plus))
y_val = 0x7fff;
if (drv->button && drv->button(joypad_info->joy_idx, key_minus))
y_val += -0x7fff;
}
*out_y = y_val;
}
}
return true;
}
void input_keyboard_line_append(struct input_keyboard_line *kb_line,
const char *word, size_t len)
{
/* Need room for current content + inserted text + NUL terminator.
* new_size is the string length after insertion (not counting NUL). */
size_t new_size = kb_line->size + len;
/* Overflow guard */
if (new_size < kb_line->size || new_size < len)
return;
/* Grow buffer with exponential strategy if needed */
if (new_size > kb_line->capacity)
{
/* At least double the current capacity, with a minimum of 64 */
size_t new_cap = kb_line->capacity ? kb_line->capacity * 2 : 64;
char *newbuf;
while (new_cap < new_size)
new_cap *= 2;
/* +1 for NUL terminator */
newbuf = (char*)realloc(kb_line->buffer, new_cap + 1);
if (!newbuf)
return;
kb_line->buffer = newbuf;
kb_line->capacity = new_cap;
}
/* Shift existing content after insertion point to make room */
memmove(
kb_line->buffer + kb_line->ptr + len,
kb_line->buffer + kb_line->ptr,
kb_line->size - kb_line->ptr);
/* Insert new text at cursor */
memcpy(kb_line->buffer + kb_line->ptr, word, len);
kb_line->ptr += len;
kb_line->size = new_size;
kb_line->buffer[kb_line->size] = '\0';
}
void input_keyboard_line_clear(input_driver_state_t *input_st)
{
if (input_st->keyboard_line.buffer)
free(input_st->keyboard_line.buffer);
input_st->keyboard_line.buffer = NULL;
input_st->keyboard_line.ptr = 0;
input_st->keyboard_line.size = 0;
input_st->keyboard_line.capacity = 0;
}
void input_keyboard_line_free(input_driver_state_t *input_st)
{
input_keyboard_line_t *kb_line = (input_keyboard_line_t*)&input_st->keyboard_line;
if (kb_line->buffer)
free(kb_line->buffer);
kb_line->buffer = NULL;
kb_line->ptr = 0;
kb_line->size = 0;
kb_line->capacity = 0;
kb_line->cb = NULL;
kb_line->userdata = NULL;
kb_line->enabled = false;
}
const char **input_keyboard_start_line(
void *userdata,
struct input_keyboard_line *kb_line,
input_keyboard_line_complete_t cb)
{
kb_line->buffer = NULL;
kb_line->ptr = 0;
kb_line->size = 0;
kb_line->capacity = 0;
kb_line->cb = cb;
kb_line->userdata = userdata;
kb_line->enabled = true;
return (const char**)&kb_line->buffer;
}
#ifdef HAVE_OVERLAY
static int16_t input_overlay_device_mouse_state(
input_overlay_t *ol, unsigned id)
{
int16_t res;
input_overlay_pointer_state_t *ptr_st = &ol->pointer_state;
switch(id)
{
case RETRO_DEVICE_ID_MOUSE_X:
ptr_st->device_mask |= (1 << RETRO_DEVICE_MOUSE);
res = (ptr_st->mouse.scale_x)
* (ptr_st->screen_x - ptr_st->mouse.prev_screen_x);
return res;
case RETRO_DEVICE_ID_MOUSE_Y:
res = (ptr_st->mouse.scale_y)
* (ptr_st->screen_y - ptr_st->mouse.prev_screen_y);
return res;
case RETRO_DEVICE_ID_MOUSE_LEFT:
return (ptr_st->mouse.click & 0x1)
|| (ptr_st->mouse.hold & 0x1);
case RETRO_DEVICE_ID_MOUSE_RIGHT:
return (ptr_st->mouse.click & 0x2)
|| (ptr_st->mouse.hold & 0x2);
case RETRO_DEVICE_ID_MOUSE_MIDDLE:
return (ptr_st->mouse.click & 0x4)
|| (ptr_st->mouse.hold & 0x4);
default:
break;
}
return 0;
}
static int16_t input_overlay_lightgun_state(
bool input_overlay_lightgun_allow_offscreen,
input_overlay_t *ol, unsigned id)
{
unsigned rarch_id;
input_overlay_pointer_state_t *ptr_st = &ol->pointer_state;
switch(id)
{
/* Pointer positions have been clamped earlier in input drivers, *
* so if we want to pass true offscreen value, it must be detected */
case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X:
ptr_st->device_mask |= (1 << RETRO_DEVICE_LIGHTGUN);
if ( ( ptr_st->ptr[0].x > -0x7fff && ptr_st->ptr[0].x != 0x7fff)
|| !input_overlay_lightgun_allow_offscreen)
return ptr_st->ptr[0].x;
return -0x8000;
case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y:
if ( ( ptr_st->ptr[0].y > -0x7fff && ptr_st->ptr[0].y != 0x7fff)
|| !input_overlay_lightgun_allow_offscreen)
return ptr_st->ptr[0].y;
return -0x8000;
case RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN:
ptr_st->device_mask |= (1 << RETRO_DEVICE_LIGHTGUN);
return ( input_overlay_lightgun_allow_offscreen
&& input_driver_pointer_is_offscreen(ptr_st->ptr[0].x, ptr_st->ptr[0].y));
case RETRO_DEVICE_ID_LIGHTGUN_AUX_A:
case RETRO_DEVICE_ID_LIGHTGUN_AUX_B:
case RETRO_DEVICE_ID_LIGHTGUN_AUX_C:
case RETRO_DEVICE_ID_LIGHTGUN_TRIGGER:
case RETRO_DEVICE_ID_LIGHTGUN_START:
case RETRO_DEVICE_ID_LIGHTGUN_PAUSE:
case RETRO_DEVICE_ID_LIGHTGUN_SELECT:
case RETRO_DEVICE_ID_LIGHTGUN_RELOAD:
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP:
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN:
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT:
case RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT:
rarch_id = input_driver_lightgun_id_convert(id);
break;
default:
rarch_id = RARCH_BIND_LIST_END;
break;
}
return ( rarch_id < RARCH_BIND_LIST_END
&& ( ptr_st->lightgun.multitouch_id == rarch_id
|| BIT256_GET(ol->overlay_state.buttons, rarch_id)));
}
static int16_t input_overlay_pointer_state(input_overlay_t *ol,
input_overlay_pointer_state_t *ptr_st,
unsigned idx, unsigned id)
{
ptr_st->device_mask |= (1 << RETRO_DEVICE_POINTER);
switch (id)
{
case RETRO_DEVICE_ID_POINTER_X:
return ptr_st->ptr[idx].x;
case RETRO_DEVICE_ID_POINTER_Y:
return ptr_st->ptr[idx].y;
case RETRO_DEVICE_ID_POINTER_PRESSED:
return (idx < ptr_st->count)
&& ptr_st->ptr[idx].x != -0x8000
&& ptr_st->ptr[idx].y != -0x8000;
case RETRO_DEVICE_ID_POINTER_COUNT:
return ptr_st->count;
case RETRO_DEVICE_ID_POINTER_IS_OFFSCREEN:
return input_driver_pointer_is_offscreen(ptr_st->ptr[idx].x, ptr_st->ptr[idx].y);
}
return 0;
}
static int16_t input_overlay_pointing_device_state(
int input_overlay_lightgun_port,
bool input_overlay_lightgun_allow_offscreen,
input_overlay_t *ol, unsigned port, unsigned device,
unsigned idx, unsigned id)
{
switch (device)
{
case RETRO_DEVICE_MOUSE:
return input_overlay_device_mouse_state(ol, id);
case RETRO_DEVICE_LIGHTGUN:
if ( input_overlay_lightgun_port == -1
|| input_overlay_lightgun_port == (int)port)
return input_overlay_lightgun_state(
input_overlay_lightgun_allow_offscreen,
ol, id);
break;
case RETRO_DEVICE_POINTER:
return input_overlay_pointer_state(ol,
(input_overlay_pointer_state_t*)&ol->pointer_state,
idx, id);
default:
break;
}
return 0;
}
#endif
#if defined(HAVE_NETWORKING) && defined(HAVE_NETWORKGAMEPAD)
static bool input_remote_init_network(input_remote_t *handle,
uint16_t port, unsigned user)
{
int fd;
struct addrinfo *res = NULL;
port = port + user;
if (!network_init())
return false;
RARCH_LOG("[Network] Bringing up remote interface on port %hu.\n",
(unsigned short)port);
if ((fd = socket_init((void**)&res, port, NULL, SOCKET_TYPE_DATAGRAM, AF_INET)) >= 0)
{
handle->net_fd[user] = fd;
if (socket_nonblock(handle->net_fd[user]))
{
if (socket_bind(handle->net_fd[user], res))
{
freeaddrinfo_retro(res);
return true;
}
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_BIND_SOCKET));
}
}
if (res)
freeaddrinfo_retro(res);
return false;
}
void input_remote_free(input_remote_t *handle, unsigned max_users)
{
int user;
for (user = 0; user < (int)max_users; user ++)
socket_close(handle->net_fd[user]);
free(handle);
}
static input_remote_t *input_remote_new(
settings_t *settings,
uint16_t port, unsigned max_users)
{
int user;
input_remote_t *handle = (input_remote_t*)
calloc(1, sizeof(*handle));
if (!handle)
return NULL;
for (user = 0; user < (int)max_users; user++)
{
handle->net_fd[user] = -1;
if (settings->bools.network_remote_enable_user[user])
if (!input_remote_init_network(handle, port, user))
{
input_remote_free(handle, max_users);
return NULL;
}
}
return handle;
}
static void input_remote_parse_packet(
input_remote_state_t *input_state,
struct remote_message *msg, unsigned user)
{
/* Parse message */
switch (msg->device)
{
case RETRO_DEVICE_JOYPAD:
if (msg->id < 16)
{
input_state->buttons[user] &= ~(1 << msg->id);
if (msg->state)
input_state->buttons[user] |= 1 << msg->id;
}
break;
case RETRO_DEVICE_ANALOG:
if (msg->id<2 && msg->index<2)
input_state->analog[msg->index * 2 + msg->id][user] = msg->state;
break;
}
}
input_remote_t *input_driver_init_remote(
settings_t *settings,
unsigned num_active_users)
{
return input_remote_new(settings,
settings->uints.network_remote_base_port,
num_active_users);
}
#endif
static int16_t input_state_device(
input_driver_state_t *input_st,
settings_t *settings,
input_mapper_t *handle,
unsigned input_analog_dpad_mode,
int32_t ret,
unsigned port, unsigned device,
unsigned idx, unsigned id,
bool button_mask)
{
int16_t res = 0;
switch (device)
{
case RETRO_DEVICE_JOYPAD:
if (id < RARCH_FIRST_META_KEY)
{
#ifdef HAVE_NETWORKGAMEPAD
/* Don't process binds if input is coming from Remote RetroPad */
if ( input_st->remote
&& INPUT_REMOTE_KEY_PRESSED(input_st, id, port))
res |= 1;
else
#endif
{
bool bind_valid = input_st->libretro_input_binds[port]
&& (*input_st->libretro_input_binds[port])[id].valid;
unsigned remap_button = settings->uints.input_remap_ids[port][id];
/* TODO/FIXME: What on earth is this code doing...? */
if (!(bind_valid && (id != remap_button)))
{
if (button_mask)
{
if (ret & (1 << id))
res |= (1 << id);
}
else
res = ret;
}
if (BIT256_GET(handle->buttons[port], id))
res = 1;
#ifdef HAVE_OVERLAY
/* Check if overlay is active and button
* corresponding to 'id' has been pressed */
if ( (port == 0)
&& input_st->overlay_ptr
&& (input_st->overlay_ptr->flags & INPUT_OVERLAY_ALIVE)
&& BIT256_GET(input_st->overlay_ptr->overlay_state.buttons, id))
{
#ifdef HAVE_MENU
bool menu_driver_alive = (menu_state_get_ptr()->flags &
MENU_ST_FLAG_ALIVE) ? true : false;
#else
bool menu_driver_alive = false;
#endif
bool input_remap_binds_enable = settings->bools.input_remap_binds_enable;
/* This button has already been processed
* inside input_driver_poll() if all the
* following are true:
* > Menu driver is not running
* > Input remaps are enabled
* > 'id' is not equal to remapped button index
* If these conditions are met, input here
* is ignored */
if ( (menu_driver_alive
|| !input_remap_binds_enable)
|| (id == remap_button))
res |= 1;
}
#endif
}
if (id <= RETRO_DEVICE_ID_JOYPAD_R3)
{
uint8_t turbo_period = settings->uints.input_turbo_period;
uint8_t turbo_duty_cycle = settings->uints.input_turbo_duty_cycle;
uint8_t turbo_mode = settings->uints.input_turbo_mode;
/* Apply hold button logic.
* When hold modifier is pressed, tapping buttons toggles their held state.
* Held buttons report as pressed even when not physically touched.
* Physical presses pass through normally (no modification). */
/* Handle hold modifier state and toggle logic */
if (!input_st->hold_btns.frame_enable[port])
{
/* Hold modifier not pressed - clear edge detection state */
input_st->hold_btns.hold_pressed[port] = 0;
}
else
{
/* Hold modifier is pressed - handle toggle on rising edge */
if (!res)
input_st->hold_btns.hold_pressed[port] &= ~(1 << id);
else if (!(input_st->hold_btns.hold_pressed[port] & (1 << id)))
{
/* Rising edge - toggle hold for this button */
input_st->hold_btns.hold_pressed[port] |= (1 << id);
input_st->hold_btns.enable[port] ^= (1 << id);
}
}
/* Apply hold effect: if button is held and not physically pressed */
if (!res && (input_st->hold_btns.enable[port] & (1 << id)))
res = 1;
/* Apply turbo button if activated. */
/* Don't allow classic mode turbo for D-pad unless explicitly allowed. */
if ( turbo_mode <= INPUT_TURBO_MODE_CLASSIC_TOGGLE
&& !settings->bools.input_turbo_allow_dpad
&& id >= RETRO_DEVICE_ID_JOYPAD_UP
&& id <= RETRO_DEVICE_ID_JOYPAD_RIGHT)
break;
if (turbo_duty_cycle == 0)
turbo_duty_cycle = turbo_period / 2;
/* Clear underlying button to prevent duplicates. */
if (input_st->turbo_btns.frame_enable[port])
{
int turbo_bind = settings->ints.input_turbo_bind;
unsigned remap_bind = settings->uints.input_remap_ids[port][turbo_bind];
if (id == remap_bind)
res = 0;
}
if (turbo_mode > INPUT_TURBO_MODE_CLASSIC_TOGGLE)
{
unsigned turbo_button = settings->uints.input_turbo_button;
unsigned remap_button = settings->uints.input_remap_ids[port][turbo_button];
/* Single button modes only care about the defined button. */
if (id != remap_button)
break;
/* Pressing turbo bind toggles turbo button on or off.
* Holding the button will pass through, else
* the pressed state will be modulated by a
* periodic pulse defined by the configured duty cycle.
*/
/* Avoid detecting the turbo button being held as multiple toggles */
if (!input_st->turbo_btns.frame_enable[port])
input_st->turbo_btns.turbo_pressed[port] &= ~(1 << 31);
else if (input_st->turbo_btns.turbo_pressed[port] >= 0)
{
input_st->turbo_btns.turbo_pressed[port] |= (1 << 31);
/* Toggle turbo for selected button. */
if (input_st->turbo_btns.enable[port] != (1 << id))
input_st->turbo_btns.enable[port] = (1 << id);
input_st->turbo_btns.mode1_enable[port] ^= 1;
}
if (input_st->turbo_btns.turbo_pressed[port] & (1 << 31))
{
/* Avoid detecting buttons being held as multiple toggles */
if (!res)
input_st->turbo_btns.turbo_pressed[port] &= ~(1 << id);
else if (!(input_st->turbo_btns.turbo_pressed[port] & (1 << id))
&& turbo_mode == INPUT_TURBO_MODE_SINGLEBUTTON)
{
uint16_t enable_new;
input_st->turbo_btns.turbo_pressed[port] |= 1 << id;
enable_new = input_st->turbo_btns.enable[port] ^ (1 << id);
if (enable_new)
input_st->turbo_btns.enable[port] = enable_new;
}
}
/* Hold mode stops turbo on release */
else if ((turbo_mode == INPUT_TURBO_MODE_SINGLEBUTTON_HOLD)
&& (input_st->turbo_btns.enable[port])
&& (input_st->turbo_btns.mode1_enable[port]))
input_st->turbo_btns.mode1_enable[port] = 0;
if ( (!res)
&& (input_st->turbo_btns.mode1_enable[port])
&& (input_st->turbo_btns.enable[port] & (1 << id)))
res = ((input_st->turbo_btns.count % turbo_period) < turbo_duty_cycle);
}
else if (turbo_mode == INPUT_TURBO_MODE_CLASSIC)
{
/* If turbo button is held, all buttons pressed
* will go into a turbo mode. Until the button is
* released again, the input state will be modulated by a
* periodic pulse defined by the configured duty cycle.
*/
if (res)
{
if (input_st->turbo_btns.frame_enable[port])
input_st->turbo_btns.enable[port] |= (1 << id);
if (input_st->turbo_btns.enable[port] & (1 << id))
/* if turbo button is enabled for this key ID */
res = ((input_st->turbo_btns.count % turbo_period) < turbo_duty_cycle);
}
else
input_st->turbo_btns.enable[port] &= ~(1 << id);
}
else /* Classic toggle mode */
{
/* Works pretty much the same as
* classic mode above but with a
* toggle mechanic */
/* Check if it's to enable the turbo func,
* if we're still holding the button from
* previous toggle then ignore */
if ( (res)
&& (input_st->turbo_btns.frame_enable[port]))
{
if (!(input_st->turbo_btns.turbo_pressed[port] & (1 << id)))
{
input_st->turbo_btns.enable[port] ^= (1 << id);
/* Remember for the toggle check */
input_st->turbo_btns.turbo_pressed[port] |= (1 << id);
}
}
else
input_st->turbo_btns.turbo_pressed[port] &= ~(1 << id);
if (res)
{
/* If turbo button is enabled for this key ID */
if (input_st->turbo_btns.enable[port] & (1 << id))
res = ((input_st->turbo_btns.count % turbo_period) < turbo_duty_cycle);
}
}
}
}
break;
case RETRO_DEVICE_KEYBOARD:
res = ret;
if (id < RETROK_LAST)
{
#ifdef HAVE_OVERLAY
if (port == 0)
{
if (input_st->overlay_ptr
&& (input_st->overlay_ptr->flags & INPUT_OVERLAY_ALIVE))
{
input_overlay_state_t
*ol_state = &input_st->overlay_ptr->overlay_state;
if (OVERLAY_GET_KEY(ol_state, id))
res |= 1;
}
}
#endif
if (MAPPER_GET_KEY(handle, id))
res |= 1;
}
break;
case RETRO_DEVICE_ANALOG:
{
#if defined(HAVE_NETWORKGAMEPAD) || defined(HAVE_OVERLAY)
#ifdef HAVE_NETWORKGAMEPAD
input_remote_state_t
*input_state = &input_st->remote_st_ptr;
#endif
unsigned base = (idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
? 2 : 0;
if (id == RETRO_DEVICE_ID_ANALOG_Y)
base += 1;
#ifdef HAVE_NETWORKGAMEPAD
if ( input_st->remote && idx < RETRO_DEVICE_INDEX_ANALOG_BUTTON
&& input_state && input_state->analog[base][port])
res = input_state->analog[base][port];
else
#endif
#endif
{
if (id < RARCH_FIRST_META_KEY)
{
bool bind_valid = input_st->libretro_input_binds[port]
&& (*input_st->libretro_input_binds[port])[id].valid;
if (bind_valid)
{
/* reset_state - used to reset input state of a button
* when the gamepad mapper is in action for that button*/
bool reset_state = false;
if (idx < 2 && id < 2)
{
unsigned offset = RARCH_FIRST_CUSTOM_BIND +
(idx * 4) + (id * 2);
if (settings->uints.input_remap_ids[port][offset] != offset)
reset_state = true;
else if (settings->uints.input_remap_ids[port][offset + 1] != (offset+1))
reset_state = true;
if (input_st->turbo_btns.frame_enable[port])
{
int turbo_bind = settings->ints.input_turbo_bind;
unsigned remap_bind = settings->uints.input_remap_ids[port][turbo_bind];
if (offset == remap_bind || offset + 1 == remap_bind)
{
res = 0;
break;
}
}
}
if (reset_state)
res = 0;
else
{
res = ret;
#ifdef HAVE_OVERLAY
if ( (input_st->overlay_ptr)
&& (input_st->overlay_ptr->flags & INPUT_OVERLAY_ALIVE)
&& (port == 0)
&& (idx != RETRO_DEVICE_INDEX_ANALOG_BUTTON)
&& !( ( input_analog_dpad_mode == ANALOG_DPAD_LSTICK
&& idx == RETRO_DEVICE_INDEX_ANALOG_LEFT)
|| ( input_analog_dpad_mode == ANALOG_DPAD_RSTICK
&& idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
|| ( ( input_analog_dpad_mode == ANALOG_DPAD_LRSTICK
|| input_analog_dpad_mode == ANALOG_DPAD_TWINSTICK)
&& ( idx == RETRO_DEVICE_INDEX_ANALOG_LEFT
|| idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT))
)
)
{
input_overlay_state_t *ol_state =
&input_st->overlay_ptr->overlay_state;
int16_t ol_analog =
ol_state->analog[base];
/* Analog values are an integer corresponding
* to the extent of the analog motion; these
* cannot be OR'd together, we must instead
* keep the value with the largest magnitude */
if (ol_analog)
{
if (res == 0)
res = ol_analog;
else
{
int16_t ol_analog_abs = (ol_analog >= 0) ?
ol_analog : -ol_analog;
int16_t res_abs = (res >= 0) ?
res : -res;
res = (ol_analog_abs > res_abs) ?
ol_analog : res;
}
}
}
#endif
}
}
}
}
if (idx < 2 && id < 2)
{
unsigned offset = 0 + (idx * 4) + (id * 2);
int val1 = handle->analog_value[port][offset];
int val2 = handle->analog_value[port][offset+1];
/* OR'ing these analog values is 100% incorrect,
* but I have no idea what this code is supposed
* to be doing (val1 and val2 always seem to be
* zero), so I will leave it alone... */
if (val1)
res |= val1;
else if (val2)
res |= val2;
}
}
break;
case RETRO_DEVICE_MOUSE:
case RETRO_DEVICE_LIGHTGUN:
case RETRO_DEVICE_POINTER:
#ifdef HAVE_OVERLAY
if ( (input_st->overlay_ptr)
&& (input_st->overlay_ptr->flags & INPUT_OVERLAY_ENABLE)
&& (settings->bools.input_overlay_pointer_enable))
res = input_overlay_pointing_device_state(
settings->ints.input_overlay_lightgun_port,
settings->bools.input_overlay_lightgun_allow_offscreen,
input_st->overlay_ptr, port, device, idx, id);
#endif
if (res || input_st->flags & INP_FLAG_BLOCK_POINTER_INPUT)
break;
if (id < RARCH_FIRST_META_KEY)
{
bool bind_valid = input_st->libretro_input_binds[port]
&& (*input_st->libretro_input_binds[port])[id].valid;
if (bind_valid)
{
if (button_mask)
{
if (ret & (1 << id))
res |= (1 << id);
}
else
res = ret;
}
}
break;
}
return res;
}
static int16_t input_state_internal(
input_driver_state_t *input_st,
settings_t *settings,
unsigned port, unsigned device,
unsigned idx, unsigned id)
{
rarch_joypad_info_t joypad_info;
float input_analog_deadzone = settings->floats.input_analog_deadzone;
float input_analog_sensitivity = settings->floats.input_analog_sensitivity;
unsigned *input_remap_port_map = settings->uints.input_remap_port_map[port];
uint8_t max_users = settings->uints.input_max_users;
const input_device_driver_t *joypad = input_st->primary_joypad;
#ifdef HAVE_MFI
const input_device_driver_t *sec_joypad = input_st->secondary_joypad;
#else
const input_device_driver_t *sec_joypad = NULL;
#endif
uint8_t mapped_port = 0;
int16_t result = 0;
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
bool input_blocked = (menu_st->input_driver_flushing_input > 0)
|| (input_st->flags & INP_FLAG_BLOCK_LIBRETRO_INPUT);
#else
bool input_blocked = (input_st->flags & INP_FLAG_BLOCK_LIBRETRO_INPUT) ? true : false;
#endif
bool input_driver_analog_requested = input_st->analog_requested[port];
bool bitmask_enabled = false;
device &= RETRO_DEVICE_MASK;
bitmask_enabled = (device == RETRO_DEVICE_JOYPAD)
&& (id == RETRO_DEVICE_ID_JOYPAD_MASK);
joypad_info.axis_threshold = settings->floats.input_axis_threshold;
/* Cache once; flag does not change during this function */
{
bool kb_mapping_blocked = !!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED);
/* Loop over all 'physical' ports mapped to specified
* 'virtual' port index */
while ((mapped_port = *(input_remap_port_map++)) < MAX_USERS)
{
int16_t ret = 0;
int16_t port_result = 0;
uint8_t input_analog_dpad_mode = settings->uints.input_analog_dpad_mode[mapped_port];
joypad_info.joy_idx = settings->uints.input_joypad_index[mapped_port];
joypad_info.auto_binds = input_autoconf_binds[joypad_info.joy_idx];
/* Skip disabled input devices */
if (mapped_port >= max_users)
continue;
/* If core has requested analog input, disable
* analog to dpad mapping (unless forced) */
switch (input_analog_dpad_mode)
{
case ANALOG_DPAD_LSTICK:
case ANALOG_DPAD_RSTICK:
case ANALOG_DPAD_LRSTICK:
case ANALOG_DPAD_TWINSTICK:
if (input_driver_analog_requested)
input_analog_dpad_mode = ANALOG_DPAD_NONE;
break;
case ANALOG_DPAD_LSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_LSTICK;
break;
case ANALOG_DPAD_RSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_RSTICK;
break;
case ANALOG_DPAD_LRSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_LRSTICK;
break;
case ANALOG_DPAD_TWINSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_TWINSTICK;
break;
default:
break;
}
/* TODO/FIXME: This code is gibberish - a mess of nested
* refactors that make no sense whatsoever. The entire
* thing needs to be rewritten from scratch... */
ret = input_state_wrap(
input_st->current_driver,
input_st->current_data,
joypad,
sec_joypad,
&joypad_info,
(*input_st->libretro_input_binds),
kb_mapping_blocked,
mapped_port, device, idx, id);
/* Ignore analog sticks when using Analog to Digital */
if ( (device == RETRO_DEVICE_ANALOG)
&& (input_analog_dpad_mode != ANALOG_DPAD_NONE))
ret = 0;
if ( (device == RETRO_DEVICE_ANALOG)
&& (ret == 0))
{
if (input_st->libretro_input_binds[mapped_port])
{
if (idx == RETRO_DEVICE_INDEX_ANALOG_BUTTON)
{
if (id < RARCH_FIRST_CUSTOM_BIND)
{
/* TODO/FIXME: Analog buttons can only be read as analog
* when the default mapping is applied. If the user
* remaps any analog buttons, they will become 'digital'
* due to the way that mapping is handled elsewhere. We
* cannot fix this without rewriting the entire mess that
* is the input remapping system... */
bool valid_bind = (*input_st->libretro_input_binds[mapped_port])[id].valid &&
(id == settings->uints.input_remap_ids[mapped_port][id]);
if (valid_bind)
{
if (sec_joypad)
ret = input_joypad_analog_button(
input_analog_deadzone,
input_analog_sensitivity,
sec_joypad, &joypad_info,
id,
&(*input_st->libretro_input_binds[mapped_port])[id]);
if (joypad && (ret == 0))
ret = input_joypad_analog_button(
input_analog_deadzone,
input_analog_sensitivity,
joypad, &joypad_info,
id,
&(*input_st->libretro_input_binds[mapped_port])[id]);
}
}
}
else
{
if (sec_joypad)
ret = input_joypad_analog_axis(
input_analog_dpad_mode,
input_analog_deadzone,
input_analog_sensitivity,
sec_joypad,
&joypad_info,
idx,
id,
(*input_st->libretro_input_binds[mapped_port]));
if (joypad && (ret == 0))
ret = input_joypad_analog_axis(
input_analog_dpad_mode,
input_analog_deadzone,
input_analog_sensitivity,
joypad,
&joypad_info,
idx,
id,
(*input_st->libretro_input_binds[mapped_port]));
}
}
}
if (!input_blocked)
{
input_mapper_t *handle = &input_st->mapper;
if (bitmask_enabled)
{
uint8_t i;
for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
if (input_state_device(input_st,
settings, handle,
input_analog_dpad_mode, ret, mapped_port,
device, idx, i, true))
port_result |= (1 << i);
}
else
port_result = input_state_device(input_st,
settings, handle,
input_analog_dpad_mode, ret, mapped_port,
device, idx, id, false);
/* Handle Analog to Digital */
if ( (device == RETRO_DEVICE_JOYPAD)
&& (input_analog_dpad_mode != ANALOG_DPAD_NONE)
&& joypad
)
{
int16_t ret_axis;
uint8_t s;
uint8_t a;
float axis_thr = joypad_info.axis_threshold;
float inv_0x7fff = INV_0x7fff;
for (s = RETRO_DEVICE_INDEX_ANALOG_LEFT; s <= RETRO_DEVICE_INDEX_ANALOG_RIGHT; s++)
{
if ( (s == RETRO_DEVICE_INDEX_ANALOG_LEFT && input_analog_dpad_mode == ANALOG_DPAD_RSTICK)
|| (s == RETRO_DEVICE_INDEX_ANALOG_RIGHT && input_analog_dpad_mode == ANALOG_DPAD_LSTICK))
continue;
for (a = RETRO_DEVICE_ID_ANALOG_X; a <= RETRO_DEVICE_ID_ANALOG_Y; a++)
{
ret_axis = input_joypad_analog_axis(
ANALOG_DPAD_NONE,
settings->floats.input_analog_deadzone,
settings->floats.input_analog_sensitivity,
joypad,
&joypad_info,
s,
a,
(*input_st->libretro_input_binds[mapped_port]));
if (ret_axis)
{
float norm = (float)ret_axis * inv_0x7fff;
int bit = -1;
if (a == RETRO_DEVICE_ID_ANALOG_Y && norm < -axis_thr)
{
if (input_analog_dpad_mode == ANALOG_DPAD_TWINSTICK && s == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
bit = RETRO_DEVICE_ID_JOYPAD_X;
else
bit = RETRO_DEVICE_ID_JOYPAD_UP;
}
else if (a == RETRO_DEVICE_ID_ANALOG_Y && norm > axis_thr)
{
if (input_analog_dpad_mode == ANALOG_DPAD_TWINSTICK && s == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
bit = RETRO_DEVICE_ID_JOYPAD_B;
else
bit = RETRO_DEVICE_ID_JOYPAD_DOWN;
}
if (a == RETRO_DEVICE_ID_ANALOG_X && norm < -axis_thr)
{
if (input_analog_dpad_mode == ANALOG_DPAD_TWINSTICK && s == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
bit = RETRO_DEVICE_ID_JOYPAD_Y;
else
bit = RETRO_DEVICE_ID_JOYPAD_LEFT;
}
else if (a == RETRO_DEVICE_ID_ANALOG_X && norm > axis_thr)
{
if (input_analog_dpad_mode == ANALOG_DPAD_TWINSTICK && s == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
bit = RETRO_DEVICE_ID_JOYPAD_A;
else
bit = RETRO_DEVICE_ID_JOYPAD_RIGHT;
}
if (bit > -1)
{
if (bitmask_enabled)
port_result |= (1 << bit);
else if (id == (unsigned)bit)
{
port_result = bit;
result = 1;
}
}
}
}
}
}
}
/* Digital values are represented by a bitmap;
* we can just perform the logical OR of
* successive samples.
* Analog values are an integer corresponding
* to the extent of the analog motion; these
* cannot be OR'd together, we must instead
* keep the value with the largest magnitude */
if (device == RETRO_DEVICE_ANALOG)
{
if (result == 0)
result = port_result;
else
{
int16_t port_result_abs = (port_result >= 0)
? port_result : -port_result;
int16_t result_abs = (result >= 0)
? result : -result;
if (port_result_abs > result_abs)
result = port_result;
}
}
else
result |= port_result;
}
} /* kb_mapping_blocked scope */
return result;
}
#ifdef HAVE_OVERLAY
/**
* input_overlay_add_inputs:
* @desc : pointer to overlay description
* @ol_state : pointer to overlay state. If valid, inputs
* that are actually 'touched' on the overlay
* itself will displayed. If NULL, inputs from
* the device connected to 'port' will be displayed.
* @port : when ol_state is NULL, specifies the port of
* the input device from which input will be
* displayed.
*
* Adds inputs from current_input to the overlay, so it's displayed
* @return true if an input that is pressed will change the overlay
*/
static bool input_overlay_add_inputs_inner(overlay_desc_t *desc,
input_driver_state_t *input_st,
settings_t *settings,
input_overlay_state_t *ol_state, unsigned port)
{
switch(desc->type)
{
case OVERLAY_TYPE_BUTTONS:
{
int i;
/* Check custom binds in the mask */
for (i = 0; i < CUSTOM_BINDS_U32_COUNT; ++i)
{
/* Get bank */
uint32_t bank_mask = BITS_GET_ELEM(desc->button_mask,i);
unsigned id = i * 32;
/* Worth pursuing? Have we got any bits left in here? */
while (bank_mask)
{
/* If this bit is set then we need to query the pad
* The button must be pressed.*/
if (bank_mask & 1)
{
if (id >= RARCH_CUSTOM_BIND_LIST_END)
break;
/* Light up the button if pressed */
if ( ol_state
? !BIT256_GET(ol_state->buttons, id)
: !input_state_internal(input_st,
settings, port, RETRO_DEVICE_JOYPAD, 0, id))
{
/* We need ALL of the inputs to be active,
* abort. */
desc->touch_mask = 0;
return false;
}
desc->touch_mask |= (1 << OVERLAY_MAX_TOUCH);
}
bank_mask >>= 1;
++id;
}
}
return (desc->touch_mask != 0);
}
case OVERLAY_TYPE_ANALOG_LEFT:
case OVERLAY_TYPE_ANALOG_RIGHT:
if (ol_state)
{
unsigned index_offset = (desc->type == OVERLAY_TYPE_ANALOG_RIGHT) ? 2 : 0;
desc->touch_mask |= (
ol_state->analog[index_offset]
| ol_state->analog[index_offset + 1]) << OVERLAY_MAX_TOUCH;
}
else
{
unsigned index = (desc->type == OVERLAY_TYPE_ANALOG_RIGHT)
? RETRO_DEVICE_INDEX_ANALOG_RIGHT
: RETRO_DEVICE_INDEX_ANALOG_LEFT;
int16_t analog_x = input_state_internal(input_st, settings, port, RETRO_DEVICE_ANALOG,
index, RETRO_DEVICE_ID_ANALOG_X);
int16_t analog_y = input_state_internal(input_st, settings, port, RETRO_DEVICE_ANALOG,
index, RETRO_DEVICE_ID_ANALOG_Y);
/* Only modify overlay delta_x/delta_y values
* if we are monitoring input from a physical
* controller */
desc->delta_x = (analog_x / (float)0x8000) * (desc->range_x / 2.0f);
desc->delta_y = (analog_y / (float)0x8000) * (desc->range_y / 2.0f);
}
/* fall-through */
case OVERLAY_TYPE_DPAD_AREA:
case OVERLAY_TYPE_ABXY_AREA:
return (desc->touch_mask != 0);
case OVERLAY_TYPE_KEYBOARD:
{
bool tmp = false;
if (ol_state)
{
if (OVERLAY_GET_KEY(ol_state, desc->retro_key_idx))
tmp = true;
}
else
tmp = input_state_internal(input_st, settings, port,
RETRO_DEVICE_KEYBOARD, 0, desc->retro_key_idx);
if (tmp)
{
desc->touch_mask |= (1 << OVERLAY_MAX_TOUCH);
return true;
}
}
break;
default:
break;
}
return false;
}
static bool input_overlay_add_inputs(input_overlay_t *ol,
input_overlay_state_t *ol_state,
input_driver_state_t *input_st,
settings_t *settings,
bool show_touched, unsigned port)
{
size_t i;
bool button_pressed = false;
for (i = 0; i < ol->active->size; i++)
{
overlay_desc_t *desc = &(ol->active->descs[i]);
button_pressed |= input_overlay_add_inputs_inner(
desc, input_st,
settings,
show_touched
? ol_state
: NULL,
port);
}
return button_pressed;
}
static void input_overlay_get_eightway_slope_limits(
const unsigned diagonal_sensitivity,
float* low_slope, float* high_slope)
{
/* Sensitivity setting is the relative size of diagonal zones to
* cardinal zones. Convert to fraction of 45 deg span (max diagonal).
*/
float f = 2.0f * diagonal_sensitivity
/ (100.0f + diagonal_sensitivity);
float high_angle /* 67.5 deg max */
= (f * (0.375 * M_PI) + (1.0f - f) * (0.25 * M_PI));
float low_angle /* 22.5 deg min */
= (f * (0.125 * M_PI) + (1.0f - f) * (0.25 * M_PI));
*high_slope = tan(high_angle);
*low_slope = tan(low_angle);
}
/**
* input_overlay_set_eightway_diagonal_sensitivity:
*
* Gets the slope limits defining each eightway type's diagonal zones.
*/
void input_overlay_set_eightway_diagonal_sensitivity(void)
{
settings_t *settings = config_get_ptr();
input_driver_state_t *input_st = &input_driver_st;
input_overlay_get_eightway_slope_limits(
settings->uints.input_overlay_dpad_diagonal_sensitivity,
&input_st->overlay_eightway_dpad_slopes[0],
&input_st->overlay_eightway_dpad_slopes[1]);
input_overlay_get_eightway_slope_limits(
settings->uints.input_overlay_abxy_diagonal_sensitivity,
&input_st->overlay_eightway_abxy_slopes[0],
&input_st->overlay_eightway_abxy_slopes[1]);
}
/**
* input_overlay_get_eightway_state:
* @desc : overlay descriptor handle for an eightway area
* @out : current input state to be OR'd with eightway state
* @x_dist : X offset from eightway area center
* @y_dist : Y offset from eightway area center
*
* Gets the eightway area's current input state based on (@x_dist, @y_dist).
**/
static INLINE void input_overlay_get_eightway_state(
const struct overlay_desc *desc,
overlay_eightway_config_t *eightway,
input_bits_t *out,
float x_dist, float y_dist)
{
uint32_t *data;
float abs_x, abs_y;
x_dist /= desc->range_x;
y_dist /= desc->range_y;
abs_x = fabsf(x_dist);
abs_y = fabsf(y_dist);
/* Compare abs_y against slope * abs_x instead of computing
* abs_slope = abs_y / abs_x. Avoids a division and the
* special-case for x_dist == 0 (vertical input). When
* abs_x is zero, abs_y > slope_high * 0 is always true
* for any nonzero touch, correctly selecting up/down. */
if (x_dist > 0.0f)
{
if (y_dist < 0.0f)
{
/* Q1 */
if (abs_y > *eightway->slope_high * abs_x)
data = eightway->up.data;
else if (abs_y < *eightway->slope_low * abs_x)
data = eightway->right.data;
else
data = eightway->up_right.data;
}
else
{
/* Q4 */
if (abs_y > *eightway->slope_high * abs_x)
data = eightway->down.data;
else if (abs_y < *eightway->slope_low * abs_x)
data = eightway->right.data;
else
data = eightway->down_right.data;
}
}
else
{
if (y_dist < 0.0f)
{
/* Q2 */
if (abs_y > *eightway->slope_high * abs_x)
data = eightway->up.data;
else if (abs_y < *eightway->slope_low * abs_x)
data = eightway->left.data;
else
data = eightway->up_left.data;
}
else
{
/* Q3 */
if (abs_y > *eightway->slope_high * abs_x)
data = eightway->down.data;
else if (abs_y < *eightway->slope_low * abs_x)
data = eightway->left.data;
else
data = eightway->down_left.data;
}
}
bits_or_bits(out->data, data, CUSTOM_BINDS_U32_COUNT);
}
/**
* input_overlay_get_analog_state:
* @out : Overlay input state to be modified
* @desc : Overlay descriptor handle
* @base : 0 or 2 for analog_left or analog_right
* @x : X coordinate
* @y : Y coordinate
* @x_dist : X offset from analog center
* @y_dist : Y offset from analog center
* @first_touch : Set true if analog was not controlled in previous poll
*
* Gets the analog input state based on @x and @y, and applies to @out.
*/
static void input_overlay_get_analog_state(
input_overlay_state_t *out, struct overlay_desc *desc,
unsigned base, float x, float y, float *x_dist, float *y_dist,
bool first_touch)
{
float x_val, y_val;
float x_val_sat, y_val_sat;
const int b = base / 2;
static float x_center[2];
static float y_center[2];
if (first_touch)
{
unsigned recenter_zone = /* [0,100] */
config_get_ptr()->uints.input_overlay_analog_recenter_zone;
if (recenter_zone != 0)
{
float touch_dist, w;
x_val = (x - desc->x_shift) / desc->range_x;
y_val = (y - desc->y_shift) / desc->range_y;
touch_dist = sqrt((x_val * x_val + y_val * y_val) * 1e4);
/* Inside zone, recenter to first touch.
* Outside zone, recenter to zone perimeter. */
if (touch_dist <= recenter_zone || recenter_zone >= 100)
w = 0.0f;
else
w = (touch_dist - recenter_zone) / touch_dist;
x_center[b] = x * (1.0f - w) + desc->x_shift * w;
y_center[b] = y * (1.0f - w) + desc->y_shift * w;
}
else
{
x_center[b] = desc->x_shift;
y_center[b] = desc->y_shift;
}
}
*x_dist = x - x_center[b];
*y_dist = y - y_center[b];
x_val = *x_dist / desc->range_x;
y_val = *y_dist / desc->range_y;
x_val_sat = x_val / desc->analog_saturate_pct;
y_val_sat = y_val / desc->analog_saturate_pct;
out->analog[base + 0] = clamp_float(x_val_sat, -1.0f, 1.0f) * 32767.0f;
out->analog[base + 1] = clamp_float(y_val_sat, -1.0f, 1.0f) * 32767.0f;
}
/**
* input_overlay_coords_inside_hitbox:
* @desc : Overlay descriptor handle.
* @x : X coordinate value.
* @y : Y coordinate value.
* @use_range_mod : Set true to use range_mod hitbox
*
* Check whether the given @x and @y coordinates of the overlay
* descriptor @desc is inside the overlay descriptor's hitbox.
*
* Returns: true (1) if X, Y coordinates are inside a hitbox,
* otherwise false (0).
**/
static bool input_overlay_coords_inside_hitbox(const struct overlay_desc *desc,
float x, float y, bool use_range_mod)
{
float range_x, range_y;
if (use_range_mod)
{
range_x = desc->range_x_mod;
range_y = desc->range_y_mod;
}
else
{
range_x = desc->range_x_hitbox;
range_y = desc->range_y_hitbox;
}
switch (desc->hitbox)
{
case OVERLAY_HITBOX_RADIAL:
{
/* Ellipse. */
float x_dist = (x - desc->x_hitbox) / range_x;
float y_dist = (y - desc->y_hitbox) / range_y;
float sq_dist = x_dist * x_dist + y_dist * y_dist;
return (sq_dist <= 1.0f);
}
case OVERLAY_HITBOX_RECT:
return
(fabs(x - desc->x_hitbox) <= range_x)
&& (fabs(y - desc->y_hitbox) <= range_y);
case OVERLAY_HITBOX_NONE:
break;
}
return false;
}
/**
* input_overlay_poll:
* @out : Polled output data.
* @touch_idx : Touch pointer index.
* @old_touch_idx : Touch pointer index from previous poll, or -1.
* @norm_x : Normalized X coordinate.
* @norm_y : Normalized Y coordinate.
* @touch_scale : Overlay scale.
*
* Polls input overlay for a single touch pointer.
*
* @norm_x and @norm_y are the result of
* video_driver_translate_coord_viewport().
*
* @return true if touch pointer is inside any hitbox
**/
static bool input_overlay_poll(
input_overlay_t *ol,
input_overlay_state_t *out,
int touch_idx, int old_touch_idx,
int16_t norm_x, int16_t norm_y, float touch_scale)
{
size_t i, j;
struct overlay_desc *descs = ol->active->descs;
unsigned int highest_prio = 0;
bool any_hitbox_pressed = false;
bool use_range_mod;
/* norm_x and norm_y is in [-0x7fff, 0x7fff] range,
* like RETRO_DEVICE_POINTER. */
float x = (float)(norm_x + 0x7fff) / 0xffff;
float y = (float)(norm_y + 0x7fff) / 0xffff;
x -= ol->active->mod_x;
y -= ol->active->mod_y;
x /= ol->active->mod_w;
y /= ol->active->mod_h;
x *= touch_scale;
y *= touch_scale;
for (i = 0; i < ol->active->size; i++)
{
float x_dist, y_dist;
unsigned int base = 0;
unsigned int desc_prio = 0;
struct overlay_desc *desc = &descs[i];
/* Use range_mod if this touch pointer contributed
* to desc's touch_mask in the previous poll */
use_range_mod = (old_touch_idx != -1)
&& BIT32_GET(desc->old_touch_mask, old_touch_idx);
if (!input_overlay_coords_inside_hitbox(desc, x, y, use_range_mod))
continue;
/* Check for exclusive hitbox, which blocks other input.
* range_mod_exclusive has priority over exclusive. */
if (use_range_mod && (desc->flags & OVERLAY_DESC_RANGE_MOD_EXCLUSIVE))
desc_prio = 2;
else if (desc->flags & OVERLAY_DESC_EXCLUSIVE)
desc_prio = 1;
if (highest_prio > desc_prio)
continue;
if (desc_prio > highest_prio)
{
highest_prio = desc_prio;
memset(out, 0, sizeof(*out));
for (j = 0; j < i; j++)
BIT32_CLEAR(descs[j].touch_mask, touch_idx);
}
BIT32_SET(desc->touch_mask, touch_idx);
x_dist = x - desc->x_shift;
y_dist = y - desc->y_shift;
switch (desc->type)
{
case OVERLAY_TYPE_BUTTONS:
bits_or_bits(out->buttons.data,
desc->button_mask.data,
ARRAY_SIZE(desc->button_mask.data));
if (BIT256_GET(desc->button_mask, RARCH_OVERLAY_NEXT))
ol->next_index = desc->next_index;
break;
case OVERLAY_TYPE_KEYBOARD:
if (desc->retro_key_idx < RETROK_LAST)
OVERLAY_SET_KEY(out, desc->retro_key_idx);
break;
case OVERLAY_TYPE_DPAD_AREA:
case OVERLAY_TYPE_ABXY_AREA:
/* Gate on eightway_config because task_overlay_desc_
* populate_eightway_config can legitimately fail its
* calloc under OOM and leave this field NULL for an
* area-type desc that requires it. Without the
* gate input_overlay_get_eightway_state NULL-derefs
* on eightway->slope_high below. */
if (desc->eightway_config)
input_overlay_get_eightway_state(
desc, desc->eightway_config,
&out->buttons, x_dist, y_dist);
break;
case OVERLAY_TYPE_ANALOG_RIGHT:
base = 2;
/* fall-through */
default:
input_overlay_get_analog_state(
out, desc, base, x, y,
&x_dist, &y_dist, !use_range_mod);
break;
}
if (desc->flags & OVERLAY_DESC_MOVABLE)
{
desc->delta_x = clamp_float(x_dist, -desc->range_x, desc->range_x)
* ol->active->mod_w;
desc->delta_y = clamp_float(y_dist, -desc->range_y, desc->range_y)
* ol->active->mod_h;
}
any_hitbox_pressed = true;
}
if (ol->flags & INPUT_OVERLAY_BLOCKED)
memset(out, 0, sizeof(*out));
return any_hitbox_pressed;
}
/**
* input_overlay_update_desc_geom:
* @ol : overlay handle.
* @desc : overlay descriptors handle.
*
* Update input overlay descriptors' vertex geometry.
**/
static void input_overlay_update_desc_geom(input_overlay_t *ol,
struct overlay_desc *desc)
{
if (!desc->image.pixels || !(desc->flags & OVERLAY_DESC_MOVABLE))
return;
if (ol->iface->vertex_geom)
ol->iface->vertex_geom(ol->iface_data, desc->image_index,
desc->mod_x + desc->delta_x, desc->mod_y + desc->delta_y,
desc->mod_w, desc->mod_h);
desc->delta_x = 0.0f;
desc->delta_y = 0.0f;
}
/**
* input_overlay_post_poll:
*
* Called after all the input_overlay_poll() calls to
* update alpha mods for pressed/unpressed controls
**/
static void input_overlay_post_poll(
enum overlay_visibility *visibility,
input_overlay_t *ol,
bool show_input, float opacity)
{
size_t i;
input_overlay_set_alpha_mod(visibility, ol, opacity);
for (i = 0; i < ol->active->size; i++)
{
struct overlay_desc *desc = &ol->active->descs[i];
if ( desc->touch_mask != 0
&& show_input && desc->image.pixels
&& ol->iface->set_alpha)
ol->iface->set_alpha(ol->iface_data, desc->image_index,
desc->alpha_mod * opacity);
input_overlay_update_desc_geom(ol, desc);
desc->old_touch_mask = desc->touch_mask;
desc->touch_mask = 0;
}
}
static void input_overlay_desc_init_hitbox(struct overlay_desc *desc)
{
desc->x_hitbox =
((desc->x_shift + desc->range_x * desc->reach_right) +
(desc->x_shift - desc->range_x * desc->reach_left)) / 2.0f;
desc->y_hitbox =
((desc->y_shift + desc->range_y * desc->reach_down) +
(desc->y_shift - desc->range_y * desc->reach_up)) / 2.0f;
desc->range_x_hitbox =
(desc->range_x * desc->reach_right +
desc->range_x * desc->reach_left) / 2.0f;
desc->range_y_hitbox =
(desc->range_y * desc->reach_down +
desc->range_y * desc->reach_up) / 2.0f;
desc->range_x_mod = desc->range_x_hitbox * desc->range_mod;
desc->range_y_mod = desc->range_y_hitbox * desc->range_mod;
}
/**
* input_overlay_scale:
* @ol : Overlay handle.
* @layout : Scale + offset factors.
*
* Scales the overlay and all its associated descriptors
* and applies any aspect ratio/offset factors.
**/
static void input_overlay_scale(struct overlay *ol,
const overlay_layout_t *layout)
{
size_t i;
ol->mod_w = ol->w * layout->x_scale;
ol->mod_h = ol->h * layout->y_scale;
ol->mod_x = (ol->center_x + (ol->x - ol->center_x) *
layout->x_scale) + layout->x_offset;
ol->mod_y = (ol->center_y + (ol->y - ol->center_y) *
layout->y_scale) + layout->y_offset;
for (i = 0; i < ol->size; i++)
{
struct overlay_desc *desc = &ol->descs[i];
float x_shift_offset = 0.0f;
float y_shift_offset = 0.0f;
float scale_w;
float scale_h;
float adj_center_x;
float adj_center_y;
/* Apply 'x separation' factor */
if (desc->x < (0.5f - 0.0001f))
x_shift_offset = layout->x_separation * -1.0f;
else if (desc->x > (0.5f + 0.0001f))
x_shift_offset = layout->x_separation;
desc->x_shift = desc->x + x_shift_offset;
/* Apply 'y separation' factor */
if (desc->y < (0.5f - 0.0001f))
y_shift_offset = layout->y_separation * -1.0f;
else if (desc->y > (0.5f + 0.0001f))
y_shift_offset = layout->y_separation;
desc->y_shift = desc->y + y_shift_offset;
scale_w = ol->mod_w * desc->range_x;
scale_h = ol->mod_h * desc->range_y;
adj_center_x = ol->mod_x + desc->x_shift * ol->mod_w;
adj_center_y = ol->mod_y + desc->y_shift * ol->mod_h;
desc->mod_w = 2.0f * scale_w;
desc->mod_h = 2.0f * scale_h;
desc->mod_x = adj_center_x - scale_w;
desc->mod_y = adj_center_y - scale_h;
input_overlay_desc_init_hitbox(desc);
}
}
static void input_overlay_parse_layout(
const struct overlay *ol,
const overlay_layout_desc_t *layout_desc,
float display_aspect_ratio,
overlay_layout_t *overlay_layout)
{
/* Set default values */
overlay_layout->x_scale = 1.0f;
overlay_layout->y_scale = 1.0f;
overlay_layout->x_separation = 0.0f;
overlay_layout->y_separation = 0.0f;
overlay_layout->x_offset = 0.0f;
overlay_layout->y_offset = 0.0f;
/* Perform auto-scaling, if required */
if (layout_desc->auto_scale)
{
/* Sanity check - if scaling is blocked,
* or aspect ratios are invalid, then we
* can do nothing */
if ( (ol->flags & OVERLAY_BLOCK_SCALE)
|| (ol->aspect_ratio <= 0.0f)
|| (display_aspect_ratio <= 0.0f))
return;
/* If display is wider than overlay,
* reduce width */
if (display_aspect_ratio > ol->aspect_ratio)
{
overlay_layout->x_scale = ol->aspect_ratio /
display_aspect_ratio;
if (overlay_layout->x_scale <= 0.0f)
{
overlay_layout->x_scale = 1.0f;
return;
}
/* If auto-scale X separation is enabled, move elements
* horizontally towards the edges of the screen */
if (ol->flags & OVERLAY_AUTO_X_SEPARATION)
overlay_layout->x_separation = ((1.0f / overlay_layout->x_scale) - 1.0f) * 0.5f;
}
/* If display is taller than overlay,
* reduce height */
else
{
overlay_layout->y_scale = display_aspect_ratio /
ol->aspect_ratio;
if (overlay_layout->y_scale <= 0.0f)
{
overlay_layout->y_scale = 1.0f;
return;
}
/* If auto-scale Y separation is enabled, move elements
* vertically towards the edges of the screen */
if (ol->flags & OVERLAY_AUTO_Y_SEPARATION)
overlay_layout->y_separation = ((1.0f / overlay_layout->y_scale) - 1.0f) * 0.5f;
}
return;
}
/* Regular 'manual' scaling/position adjustment
* > Landscape display orientations */
if (display_aspect_ratio > 1.0f)
{
float scale = layout_desc->scale_landscape;
float aspect_adjust = layout_desc->aspect_adjust_landscape;
/* Note: Y offsets have their sign inverted,
* since from a usability perspective positive
* values should move the overlay upwards */
overlay_layout->x_offset = layout_desc->x_offset_landscape;
overlay_layout->y_offset = layout_desc->y_offset_landscape * -1.0f;
if (!(ol->flags & OVERLAY_BLOCK_X_SEPARATION))
overlay_layout->x_separation = layout_desc->x_separation_landscape;
if (!(ol->flags & OVERLAY_BLOCK_Y_SEPARATION))
overlay_layout->y_separation = layout_desc->y_separation_landscape;
if (!(ol->flags & OVERLAY_BLOCK_SCALE))
{
/* In landscape orientations, aspect correction
* adjusts the overlay width */
overlay_layout->x_scale = (aspect_adjust >= 0.0f) ?
(scale * (aspect_adjust + 1.0f)) :
(scale / ((aspect_adjust * -1.0f) + 1.0f));
overlay_layout->y_scale = scale;
}
}
/* > Portrait display orientations */
else
{
float scale = layout_desc->scale_portrait;
float aspect_adjust = layout_desc->aspect_adjust_portrait;
overlay_layout->x_offset = layout_desc->x_offset_portrait;
overlay_layout->y_offset = layout_desc->y_offset_portrait * -1.0f;
if (!(ol->flags & OVERLAY_BLOCK_X_SEPARATION))
overlay_layout->x_separation = layout_desc->x_separation_portrait;
if (!(ol->flags & OVERLAY_BLOCK_Y_SEPARATION))
overlay_layout->y_separation = layout_desc->y_separation_portrait;
if (!(ol->flags & OVERLAY_BLOCK_SCALE))
{
/* In portrait orientations, aspect correction
* adjusts the overlay height */
overlay_layout->x_scale = scale;
overlay_layout->y_scale = (aspect_adjust >= 0.0f) ?
(scale * (aspect_adjust + 1.0f)) :
(scale / ((aspect_adjust * -1.0f) + 1.0f));
}
}
}
static void input_overlay_set_vertex_geom(input_overlay_t *ol)
{
size_t i;
if (!ol->iface->vertex_geom)
return;
if (ol->active->image.pixels)
ol->iface->vertex_geom(ol->iface_data, 0,
ol->active->mod_x, ol->active->mod_y,
ol->active->mod_w, ol->active->mod_h);
for (i = 0; i < ol->active->size; i++)
{
struct overlay_desc *desc = &ol->active->descs[i];
if (desc->image.pixels)
ol->iface->vertex_geom(ol->iface_data, desc->image_index,
desc->mod_x, desc->mod_y, desc->mod_w, desc->mod_h);
}
}
/**
* input_overlay_set_scale_factor:
* @ol : Overlay handle.
* @layout_desc : Scale + offset factors.
*
* Scales the overlay and applies any aspect ratio/
* offset factors.
**/
void input_overlay_set_scale_factor(
input_overlay_t *ol, const overlay_layout_desc_t *layout_desc,
unsigned video_driver_width,
unsigned video_driver_height
)
{
size_t i;
float display_aspect_ratio = 0.0f;
if (!ol || !layout_desc)
return;
if (video_driver_height > 0)
display_aspect_ratio = (float)video_driver_width /
(float)video_driver_height;
for (i = 0; i < ol->size; i++)
{
struct overlay *current_overlay = &ol->overlays[i];
overlay_layout_t overlay_layout;
input_overlay_parse_layout(current_overlay,
layout_desc, display_aspect_ratio, &overlay_layout);
input_overlay_scale(current_overlay, &overlay_layout);
}
input_overlay_set_vertex_geom(ol);
}
void input_overlay_load_active(
enum overlay_visibility *visibility,
input_overlay_t *ol, float opacity)
{
if (ol->iface->load)
ol->iface->load(ol->iface_data, ol->active->load_images,
ol->active->load_images_size);
input_overlay_set_alpha_mod(visibility, ol, opacity);
input_overlay_set_vertex_geom(ol);
if (ol->iface->full_screen)
ol->iface->full_screen(ol->iface_data,
(ol->active->flags & OVERLAY_FULL_SCREEN));
}
/**
* input_overlay_next_move_touch_masks
* @ol : Overlay handle.
*
* Finds similar descs in the next overlay (i.e. same location and type)
* and moves touch masks from active overlay to next.
*/
void input_overlay_next_move_touch_masks(input_overlay_t *ol)
{
const struct overlay *active = ol->active;
const struct overlay *next = ol->overlays + ol->next_index;
size_t i, j;
for (i = 0; i < active->size; i++)
{
struct overlay_desc *desc = active->descs + i;
if (desc->old_touch_mask)
{
for (j = 0; j < next->size; j++)
{
struct overlay_desc *desc2 = next->descs + j;
if ( desc2->type == desc->type
&& fabs(desc2->x - desc->x) < 0.01f
&& fabs(desc2->y - desc->y) < 0.01f)
desc2->old_touch_mask = desc->old_touch_mask;
}
desc->old_touch_mask = 0;
}
}
}
/**
* input_overlay_poll_clear:
* @ol : overlay handle
*
* Call when there is nothing to poll. Allows overlay to
* clear certain state.
**/
static void input_overlay_poll_clear(
enum overlay_visibility *visibility,
input_overlay_t *ol, float opacity)
{
size_t i;
ol->flags &= ~INPUT_OVERLAY_BLOCKED;
input_overlay_set_alpha_mod(visibility, ol, opacity);
for (i = 0; i < ol->active->size; i++)
{
struct overlay_desc *desc = &ol->active->descs[i];
desc->old_touch_mask = desc->touch_mask;
desc->touch_mask = 0;
input_overlay_update_desc_geom(ol, desc);
}
}
static enum overlay_visibility input_overlay_get_visibility(
enum overlay_visibility *visibility,
int overlay_idx)
{
if (!visibility)
return OVERLAY_VISIBILITY_DEFAULT;
if ((overlay_idx < 0) || (overlay_idx >= MAX_VISIBILITY))
return OVERLAY_VISIBILITY_DEFAULT;
return visibility[overlay_idx];
}
void input_overlay_set_alpha_mod(
enum overlay_visibility *visibility,
input_overlay_t *ol, float mod)
{
unsigned i;
if (!ol)
return;
if (ol->flags & INPUT_OVERLAY_GAMEPAD_HIDDEN)
mod = 0.0f;
for (i = 0; i < ol->active->load_images_size; i++)
{
if (input_overlay_get_visibility(visibility, i)
== OVERLAY_VISIBILITY_HIDDEN)
ol->iface->set_alpha(ol->iface_data, i, 0.0);
else
ol->iface->set_alpha(ol->iface_data, i, mod);
}
}
static void input_overlay_free_images(input_overlay_t *ol)
{
size_t i;
if (!ol || !ol->images)
return;
for (i = 0; i < ol->num_images; i++)
image_texture_free(ol->images[i]);
free(ol->images);
ol->images = NULL;
}
static void input_overlay_free_overlays(input_overlay_t *ol)
{
size_t i;
if (!ol || !ol->overlays)
return;
for (i = 0; i < ol->size; i++)
input_overlay_free_overlay(&ol->overlays[i]);
free(ol->overlays);
ol->overlays = NULL;
}
void input_overlay_free_overlay(struct overlay *overlay)
{
size_t i;
if (!overlay)
return;
for (i = 0; i < overlay->size; i++)
{
if (overlay->descs[i].eightway_config)
free(overlay->descs[i].eightway_config);
overlay->descs[i].eightway_config = NULL;
}
if (overlay->load_images)
free(overlay->load_images);
overlay->load_images = NULL;
if (overlay->descs)
free(overlay->descs);
overlay->descs = NULL;
}
/**
* input_overlay_free:
* @ol : Overlay handle.
*
* Frees overlay handle.
**/
static void input_overlay_free(input_overlay_t *ol)
{
if (!ol)
return;
input_overlay_free_images(ol);
input_overlay_free_overlays(ol);
if (ol->iface && ol->iface->enable)
ol->iface->enable(ol->iface_data, false);
if (ol->path)
{
free(ol->path);
ol->path = NULL;
}
free(ol);
}
void input_overlay_auto_rotate_(
unsigned video_driver_width,
unsigned video_driver_height,
bool input_overlay_enable,
input_overlay_t *ol)
{
size_t i;
enum overlay_orientation screen_orientation = OVERLAY_ORIENTATION_PORTRAIT;
enum overlay_orientation active_overlay_orientation = OVERLAY_ORIENTATION_NONE;
bool tmp = false;
/* Sanity check */
if (!ol || !(ol->flags & INPUT_OVERLAY_ALIVE) || !input_overlay_enable)
return;
/* Get current screen orientation */
if (video_driver_width > video_driver_height)
screen_orientation = OVERLAY_ORIENTATION_LANDSCAPE;
/* Get orientation of active overlay */
if (*ol->active->name)
{
if (strstr(ol->active->name, "landscape"))
active_overlay_orientation = OVERLAY_ORIENTATION_LANDSCAPE;
else if (strstr(ol->active->name, "portrait"))
active_overlay_orientation = OVERLAY_ORIENTATION_PORTRAIT;
else /* Sanity check */
return;
}
else /* Sanity check */
return;
/* If screen and overlay have the same orientation,
* no action is required */
if (screen_orientation == active_overlay_orientation)
return;
/* Attempt to find index of overlay corresponding
* to opposite orientation */
for (i = 0; i < ol->active->size; i++)
{
overlay_desc_t *desc = &ol->active->descs[i];
if (!desc)
continue;
if (*desc->next_index_name)
{
bool next_overlay_found = false;
if (active_overlay_orientation == OVERLAY_ORIENTATION_LANDSCAPE)
next_overlay_found = (strstr(desc->next_index_name, "portrait") != 0);
else
next_overlay_found = (strstr(desc->next_index_name, "landscape") != 0);
if (next_overlay_found)
{
/* We have a valid target overlay
* > Trigger 'overly next' command event
* Note: tmp == false. This prevents CMD_EVENT_OVERLAY_NEXT
* from calling input_overlay_auto_rotate_() again */
ol->next_index = desc->next_index;
command_event(CMD_EVENT_OVERLAY_NEXT, &tmp);
break;
}
}
}
}
/**
* input_overlay_poll_lightgun
* @settings : pointer to settings
* @ol : overlay handle
* @old_ptr_count : previous poll's non-hitbox pointer count
*
* Updates multi-touch button state of the overlay lightgun.
*/
static void input_overlay_poll_lightgun(settings_t *settings,
input_overlay_t *ol, const int old_ptr_count)
{
input_overlay_pointer_state_t *ptr_st = &ol->pointer_state;
const int ptr_count = ptr_st->count;
unsigned action = OVERLAY_LIGHTGUN_ACTION_NONE;
int8_t trig_delay =
settings->uints.input_overlay_lightgun_trigger_delay;
int8_t delay_idx;
static uint16_t trig_buf;
static uint8_t now_idx;
static uint8_t peak_ptr_count;
static const unsigned action_to_id[OVERLAY_LIGHTGUN_ACTION_END] = {
RARCH_BIND_LIST_END,
RARCH_LIGHTGUN_TRIGGER,
RARCH_LIGHTGUN_RELOAD,
RARCH_LIGHTGUN_AUX_A,
RARCH_LIGHTGUN_AUX_B,
RARCH_LIGHTGUN_AUX_C,
RARCH_LIGHTGUN_START,
RARCH_LIGHTGUN_SELECT,
RARCH_LIGHTGUN_DPAD_UP,
RARCH_LIGHTGUN_DPAD_DOWN,
RARCH_LIGHTGUN_DPAD_LEFT,
RARCH_LIGHTGUN_DPAD_RIGHT
};
/* Update peak pointer count */
if (!old_ptr_count && ptr_count)
peak_ptr_count = ptr_count;
else
peak_ptr_count = MAX(ptr_count, peak_ptr_count);
/* Apply trigger delay */
now_idx = (now_idx + 1) % (OVERLAY_LIGHTGUN_TRIG_MAX_DELAY + 1);
delay_idx = (now_idx + trig_delay) % (OVERLAY_LIGHTGUN_TRIG_MAX_DELAY + 1);
if (ptr_count > 0)
BIT16_SET(trig_buf, delay_idx);
else
BIT16_CLEAR(trig_buf, delay_idx);
/* Create button input if we're past the trigger delay */
if (BIT16_GET(trig_buf, now_idx))
{
switch (peak_ptr_count)
{
case 1:
if (settings->bools.input_overlay_lightgun_trigger_on_touch)
action = OVERLAY_LIGHTGUN_ACTION_TRIGGER;
break;
case 2:
action = settings->uints.input_overlay_lightgun_two_touch_input;
break;
case 3:
action = settings->uints.input_overlay_lightgun_three_touch_input;
break;
case 4:
action = settings->uints.input_overlay_lightgun_four_touch_input;
break;
default:
break;
}
}
ptr_st->lightgun.multitouch_id = action_to_id[action];
}
static void input_overlay_get_mouse_scale(settings_t *settings,
float *scale_x, float *scale_y,
int *swipe_thres_x, int *swipe_thres_y)
{
video_driver_state_t *video_st = video_state_get_ptr();
struct retro_game_geometry *geom = &video_st->av_info.geometry;
if (geom->base_height)
{
float adj_x, adj_y;
float speed = settings->floats.input_overlay_mouse_speed;
float swipe_thres =
655.35f * settings->floats.input_overlay_mouse_swipe_threshold;
float display_aspect = (float)video_st->width / video_st->height;
float core_aspect = (float)geom->base_width / geom->base_height;
if (display_aspect > core_aspect)
{
adj_x = speed * (display_aspect / core_aspect);
adj_y = speed;
}
else
{
adj_y = speed * (core_aspect / display_aspect);
adj_x = speed;
}
*scale_x = (adj_x * geom->base_width) / (float)0x7fff;
*scale_y = (adj_y * geom->base_height) / (float)0x7fff;
if (display_aspect > 1.0f)
{
*swipe_thres_x = (int)(swipe_thres / display_aspect);
*swipe_thres_y = (int)swipe_thres;
}
else
{
*swipe_thres_x = (int)swipe_thres;
*swipe_thres_y = (int)(swipe_thres / display_aspect);
}
}
}
/**
* input_overlay_poll_mouse
* @settings : pointer to settings
* @mouse_st : pointer to overlay mouse state
* @ol : overlay handle
* @ptr_count : this poll's non-hitbox pointer count
* @old_ptr_count : previous poll's non-hitbox pointer count
*
* Updates button state of the overlay mouse.
*/
static void input_overlay_poll_mouse(settings_t *settings,
struct input_overlay_mouse_state *mouse_st,
input_overlay_t *ol,
const int ptr_count,
const int old_ptr_count)
{
input_overlay_pointer_state_t *ptr_st = &ol->pointer_state;
const retro_time_t now_usec = cpu_features_get_time_usec();
const retro_time_t hold_usec = settings->uints.input_overlay_mouse_hold_msec * 1000;
const retro_time_t dtap_usec = settings->uints.input_overlay_mouse_dtap_msec * 1000;
const uint8_t alt_2touch = settings->uints.input_overlay_mouse_alt_two_touch_input;
int swipe_thres_x = 0;
int swipe_thres_y = 0;
const bool hold_to_drag = settings->bools.input_overlay_mouse_hold_to_drag;
const bool dtap_to_drag = settings->bools.input_overlay_mouse_dtap_to_drag;
bool want_feedback = false;
bool is_swipe, is_brief, is_long;
static retro_time_t start_usec;
static retro_time_t last_down_usec;
static retro_time_t last_up_usec;
static retro_time_t pending_click_usec;
static retro_time_t click_dur_usec;
static retro_time_t click_end_usec;
static int x_start;
static int y_start;
static int peak_ptr_count;
static int old_peak_ptr_count;
static bool check_gestures;
static bool pending_click;
static const uint8_t btns[OVERLAY_MAX_TOUCH + 1] =
{0x0, 0x1, 0x2, 0x4}; /* none, lmb, rmb, mmb */
input_overlay_get_mouse_scale(settings,
(float*)&mouse_st->scale_x, &mouse_st->scale_y,
&swipe_thres_x, &swipe_thres_y);
/* Check for pointer count changes */
if (ptr_count != old_ptr_count)
{
mouse_st->click = 0;
pending_click = false;
/* Assume main pointer changed. Reset deltas */
mouse_st->prev_screen_x = x_start = ptr_st->screen_x;
mouse_st->prev_screen_y = y_start = ptr_st->screen_y;
if (ptr_count > old_ptr_count)
{
/* Pointer added */
peak_ptr_count = ptr_count;
start_usec = now_usec;
/* Alt 2-touch input. After gesture checks,
* use 2nd touch as a button */
if (!check_gestures && ptr_count == 2)
mouse_st->hold = btns[alt_2touch];
}
else
{
/* Pointer removed */
mouse_st->hold = 0;
if (!ptr_count)
old_peak_ptr_count = peak_ptr_count;
}
}
/* Action type */
is_swipe = abs(ptr_st->screen_x - x_start) > swipe_thres_x ||
abs(ptr_st->screen_y - y_start) > swipe_thres_y;
is_brief = (now_usec - start_usec) < 200000;
is_long = (now_usec - start_usec) > (hold_to_drag ? hold_usec : 250000);
/* Check if new button input should be created */
if (check_gestures)
{
if (!is_swipe)
{
if ( hold_to_drag
&& is_long && ptr_count && !mouse_st->hold)
{
/* Long press */
mouse_st->hold = btns[ptr_count];
want_feedback = true;
}
else if (is_brief)
{
if (ptr_count && !old_ptr_count)
{
/* New input. Check for double tap */
if ( dtap_to_drag
&& now_usec - last_up_usec < dtap_usec)
mouse_st->hold = btns[old_peak_ptr_count];
last_down_usec = now_usec;
}
else if (!ptr_count && old_ptr_count)
{
/* Finished a tap. Send click */
click_dur_usec = (now_usec - last_down_usec) + 5000;
if (dtap_to_drag)
{
pending_click = true;
pending_click_usec = now_usec + dtap_usec;
}
else
{
mouse_st->click = btns[peak_ptr_count];
click_end_usec = now_usec + click_dur_usec;
}
last_up_usec = now_usec;
}
}
}
else
{
/* Swiping. Stop gesture checks and possibly hold a button */
if (ptr_count > 1)
{
if (hold_to_drag && !alt_2touch)
{
mouse_st->hold = btns[ptr_count];
want_feedback = true;
}
else if (alt_2touch && !hold_to_drag
&& ptr_count == 2)
mouse_st->hold = btns[alt_2touch];
}
check_gestures = false;
}
}
/* Check for pending click */
if (pending_click && now_usec >= pending_click_usec)
{
mouse_st->click = (1 << (old_peak_ptr_count - 1));
click_end_usec = now_usec + click_dur_usec;
pending_click = false;
}
if (!ptr_count)
check_gestures = true;
else if (is_long)
check_gestures = false;
/* Remove stale clicks */
if (mouse_st->click && now_usec > click_end_usec)
mouse_st->click = 0;
if (want_feedback && settings->bools.vibrate_on_keypress)
{
input_driver_t *input = input_driver_st.current_driver;
if (input && input->keypress_vibrate)
input->keypress_vibrate();
}
}
/**
* input_overlay_track_touch_inputs
* @state : Overlay input state for this poll
* @old_state : Overlay input state for previous poll
*
* Matches current touch inputs to previous poll's, based on distance.
* Updates old_touch_index_lut and assigns -1 to any new inputs.
*/
static void input_overlay_track_touch_inputs(
input_overlay_state_t *state, input_overlay_state_t *old_state)
{
int *const old_index_lut = input_driver_st.old_touch_index_lut;
int i, j, t, new_idx;
float x_dist, y_dist, sq_dist, outlier;
float min_sq_dist[OVERLAY_MAX_TOUCH];
memset(old_index_lut, -1, sizeof(int) * OVERLAY_MAX_TOUCH);
/* Compute (squared) distances and match new indexes to old */
for (i = 0; i < state->touch_count; i++)
{
min_sq_dist[i] = 3e8f;
for (j = 0; j < old_state->touch_count; j++)
{
x_dist = state->touch[i].x - old_state->touch[j].x;
y_dist = state->touch[i].y - old_state->touch[j].y;
sq_dist = x_dist * x_dist + y_dist * y_dist;
if (sq_dist < min_sq_dist[i])
{
min_sq_dist[i] = sq_dist;
old_index_lut[i] = j;
}
}
}
/* If touch_count increased, find the outliers and assign -1 */
for (t = old_state->touch_count; t < state->touch_count; t++)
{
new_idx = OVERLAY_MAX_TOUCH - 1;
outlier = 0;
for (i = 0; i < state->touch_count; i++)
if (min_sq_dist[i] > outlier)
{
outlier = min_sq_dist[i];
new_idx = i;
min_sq_dist[i] = 0;
}
old_index_lut[new_idx] = -1;
}
}
static void input_overlay_update_pointer_coords(
input_overlay_pointer_state_t *ptr_st, int touch_idx)
{
void *input_data = input_driver_st.current_data;
input_driver_t *input = (input_driver_t*)input_driver_st.current_driver;
/* Need multi-touch coordinates for pointer only */
if ( ptr_st->count
&& !(ptr_st->device_mask & (1 << RETRO_DEVICE_POINTER)))
{
ptr_st->count++;
return;
}
/* Need viewport pointers for pointer and lightgun */
if ( ptr_st->device_mask
& ((1 << RETRO_DEVICE_LIGHTGUN) | (1 << RETRO_DEVICE_POINTER)))
{
ptr_st->ptr[ptr_st->count].x = input->input_state(
input_data, NULL, NULL, NULL, NULL, true, 0,
RETRO_DEVICE_POINTER,
touch_idx,
RETRO_DEVICE_ID_POINTER_X);
ptr_st->ptr[ptr_st->count].y = input->input_state(
input_data, NULL, NULL, NULL, NULL, true, 0,
RETRO_DEVICE_POINTER,
touch_idx,
RETRO_DEVICE_ID_POINTER_Y);
}
/* Need fullscreen pointer for mouse only */
if ( !ptr_st->count
&& (ptr_st->device_mask & (1 << RETRO_DEVICE_MOUSE)))
{
ptr_st->mouse.prev_screen_x = ptr_st->screen_x;
ptr_st->screen_x = input->input_state(
input_data, NULL, NULL, NULL, NULL, true, 0,
RARCH_DEVICE_POINTER_SCREEN,
touch_idx,
RETRO_DEVICE_ID_POINTER_X);
ptr_st->mouse.prev_screen_y = ptr_st->screen_y;
ptr_st->screen_y = input->input_state(
input_data, NULL, NULL, NULL, NULL, true, 0,
RARCH_DEVICE_POINTER_SCREEN,
touch_idx,
RETRO_DEVICE_ID_POINTER_Y);
}
ptr_st->count++;
}
/*
* input_poll_overlay:
*
* Poll pressed buttons/keys on currently active overlay.
**/
static void input_poll_overlay(
bool keyboard_mapping_blocked,
settings_t *settings,
void *ol_data,
enum overlay_visibility *overlay_visibility,
float opacity,
unsigned analog_dpad_mode,
float axis_threshold)
{
input_overlay_state_t old_ol_state;
int i, j;
input_overlay_t *ol = (input_overlay_t*)ol_data;
int blocked_touch_idx = -1;
uint16_t key_mod = 0;
uint16_t ptrdev_touch_mask = 0;
uint16_t hitbox_touch_mask = 0;
bool button_pressed = false;
input_driver_state_t *input_st = &input_driver_st;
void *input_data = input_st->current_data;
input_overlay_state_t *ol_state = &ol->overlay_state;
input_overlay_pointer_state_t *ptr_state = &ol->pointer_state;
input_driver_t *input = (input_driver_t*)input_st->current_driver;
enum overlay_show_input_type
input_overlay_show_inputs = (enum overlay_show_input_type)
settings->uints.input_overlay_show_inputs;
unsigned input_overlay_show_inputs_port = settings->uints.input_overlay_show_inputs_port;
float touch_scale = (float)settings->uints.input_touch_scale;
bool ol_ptr_enable = settings->bools.input_overlay_pointer_enable;
bool osk_state_changed = false;
static int old_ptr_count;
static int old_blocked_touch_idx;
static int16_t old_ptrdev_touch_mask;
static int16_t old_hitbox_touch_mask;
if (!ol_state)
return;
memcpy(&old_ol_state, ol_state,
sizeof(old_ol_state));
memset(ol_state, 0, sizeof(*ol_state));
if (ol_ptr_enable)
{
old_ptr_count = ptr_state->count;
ptr_state->count = 0;
}
if (input->input_state)
{
rarch_joypad_info_t joypad_info;
unsigned device = (ol->active->flags & OVERLAY_FULL_SCREEN)
? RARCH_DEVICE_POINTER_SCREEN
: RETRO_DEVICE_POINTER;
const input_device_driver_t
*joypad = input_st->primary_joypad;
#ifdef HAVE_MFI
const input_device_driver_t
*sec_joypad = input_st->secondary_joypad;
#else
const input_device_driver_t
*sec_joypad = NULL;
#endif
joypad_info.joy_idx = 0;
joypad_info.auto_binds = NULL;
joypad_info.axis_threshold = 0.0f;
/* Get driver input */
for (i = 0;
input->input_state(
input_data,
joypad,
sec_joypad,
&joypad_info,
NULL,
keyboard_mapping_blocked,
0,
device,
i,
RETRO_DEVICE_ID_POINTER_PRESSED)
&& i < OVERLAY_MAX_TOUCH;
i++)
{
ol_state->touch[i].x = input->input_state(
input_data,
joypad,
sec_joypad,
&joypad_info,
NULL,
keyboard_mapping_blocked,
0,
device,
i,
RETRO_DEVICE_ID_POINTER_X);
ol_state->touch[i].y = input->input_state(
input_data,
joypad,
sec_joypad,
&joypad_info,
NULL,
keyboard_mapping_blocked,
0,
device,
i,
RETRO_DEVICE_ID_POINTER_Y);
}
ol_state->touch_count = i;
/* Update lookup table of new to old touch indexes */
input_overlay_track_touch_inputs(ol_state, &old_ol_state);
/* Poll overlay */
for (i = 0; i < ol_state->touch_count; i++)
{
input_overlay_state_t polled_data;
int old_i = input_st->old_touch_index_lut[i];
bool hitbox_pressed = false;
if (old_i != -1)
{
/* Keep each touch pointer dedicated to the same input type
* (hitbox or pointing device) from the previous poll */
if (BIT16_GET(old_hitbox_touch_mask, old_i))
BIT16_SET(hitbox_touch_mask, i);
else if (BIT16_GET(old_ptrdev_touch_mask, old_i))
BIT16_SET(ptrdev_touch_mask, i);
/* Skip blocked touch pointer and freeze any overlay_next
* input until the blocked touch is removed */
if (old_i == old_blocked_touch_idx)
{
blocked_touch_idx = i;
if (BIT256_GET(old_ol_state.buttons, RARCH_OVERLAY_NEXT))
BIT256_SET(ol_state->buttons, RARCH_OVERLAY_NEXT);
continue;
}
}
memset(&polled_data, 0, sizeof(struct input_overlay_state));
/* Check hitboxes only if this touch pointer
* is not controlling a pointing device */
if ( ol->flags & INPUT_OVERLAY_ENABLE
&& !(ol->flags & INPUT_OVERLAY_GAMEPAD_HIDDEN)
&& !BIT16_GET(ptrdev_touch_mask, i))
hitbox_pressed = input_overlay_poll(
ol, &polled_data, i, old_i,
ol_state->touch[i].x, ol_state->touch[i].y, touch_scale);
else
ol->flags &= ~INPUT_OVERLAY_BLOCKED;
if (hitbox_pressed)
{
/* Block touch pointer if overlay_next was pressed */
if (BIT256_GET(polled_data.buttons, RARCH_OVERLAY_NEXT))
blocked_touch_idx = i;
bits_or_bits(ol_state->buttons.data,
polled_data.buttons.data,
ARRAY_SIZE(polled_data.buttons.data));
for (j = 0; j < (int)ARRAY_SIZE(ol_state->keys); j++)
ol_state->keys[j] |= polled_data.keys[j];
/* Fingers pressed later take priority and matched up
* with overlay poll priorities. */
for (j = 0; j < 4; j++)
if (polled_data.analog[j])
ol_state->analog[j] = polled_data.analog[j];
hitbox_touch_mask |= (1 << i);
}
else if ( ol_ptr_enable
&& ptr_state->device_mask
&& !BIT16_GET(hitbox_touch_mask, i)
&& !(ol->flags & INPUT_OVERLAY_BLOCKED))
{
input_overlay_update_pointer_coords(ptr_state, i);
ptrdev_touch_mask |= (1 << i);
}
}
}
if (ol_ptr_enable)
{
if (ptr_state->device_mask & (1 << RETRO_DEVICE_LIGHTGUN))
input_overlay_poll_lightgun(settings, ol, old_ptr_count);
if (ptr_state->device_mask & (1 << RETRO_DEVICE_MOUSE))
input_overlay_poll_mouse(settings, &ptr_state->mouse, ol,
ptr_state->count, old_ptr_count);
}
if ( OVERLAY_GET_KEY(ol_state, RETROK_LSHIFT)
|| OVERLAY_GET_KEY(ol_state, RETROK_RSHIFT))
key_mod |= RETROKMOD_SHIFT;
if ( OVERLAY_GET_KEY(ol_state, RETROK_LCTRL)
|| OVERLAY_GET_KEY(ol_state, RETROK_RCTRL))
key_mod |= RETROKMOD_CTRL;
if ( OVERLAY_GET_KEY(ol_state, RETROK_LALT)
|| OVERLAY_GET_KEY(ol_state, RETROK_RALT))
key_mod |= RETROKMOD_ALT;
if ( OVERLAY_GET_KEY(ol_state, RETROK_LMETA)
|| OVERLAY_GET_KEY(ol_state, RETROK_RMETA))
key_mod |= RETROKMOD_META;
/* CAPSLOCK SCROLLOCK NUMLOCK */
for (i = (int)ARRAY_SIZE(ol_state->keys); i-- > 0;)
{
if (ol_state->keys[i] != old_ol_state.keys[i])
{
uint32_t orig_bits = old_ol_state.keys[i];
uint32_t new_bits = ol_state->keys[i];
osk_state_changed = true;
for (j = 0; j < 32; j++)
if ((orig_bits & (1 << j)) != (new_bits & (1 << j)))
{
unsigned rk = i * 32 + j;
uint32_t c = input_keymaps_translate_rk_to_ascii((enum retro_key)rk, (enum retro_mod)key_mod);
input_keyboard_event(new_bits & (1 << j),
rk, c, key_mod, RETRO_DEVICE_POINTER);
}
}
}
/* Map "analog" buttons to analog axes like regular input drivers do. */
for (j = 0; j < 4; j++)
{
unsigned bind_plus = RARCH_ANALOG_LEFT_X_PLUS + 2 * j;
unsigned bind_minus = bind_plus + 1;
if (ol_state->analog[j])
continue;
if ((BIT256_GET(ol->overlay_state.buttons, bind_plus)))
ol_state->analog[j] += 0x7fff;
if ((BIT256_GET(ol->overlay_state.buttons, bind_minus)))
ol_state->analog[j] -= 0x7fff;
}
/* Check for analog_dpad_mode.
* Map analogs to d-pad buttons when configured. */
switch (analog_dpad_mode)
{
case ANALOG_DPAD_LSTICK:
case ANALOG_DPAD_RSTICK:
{
float analog_x, analog_y;
unsigned analog_base = 2;
if (analog_dpad_mode == ANALOG_DPAD_LSTICK)
analog_base = 0;
analog_x = (float)ol_state->analog[analog_base + 0] / 0x7fff;
analog_y = (float)ol_state->analog[analog_base + 1] / 0x7fff;
if (analog_x <= -axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_LEFT);
if (analog_x >= axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_RIGHT);
if (analog_y <= -axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_UP);
if (analog_y >= axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_DOWN);
break;
}
case ANALOG_DPAD_LRSTICK:
{
float analog_x, analog_y;
analog_x = (float)ol_state->analog[0] / 0x7fff;
analog_y = (float)ol_state->analog[1] / 0x7fff;
if (!analog_x)
analog_x = (float)ol_state->analog[2] / 0x7fff;
if (!analog_y)
analog_y = (float)ol_state->analog[3] / 0x7fff;
if (analog_x <= -axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_LEFT);
if (analog_x >= axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_RIGHT);
if (analog_y <= -axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_UP);
if (analog_y >= axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_DOWN);
break;
}
case ANALOG_DPAD_TWINSTICK:
{
float analog_x, analog_y;
analog_x = (float)ol_state->analog[0] / 0x7fff;
analog_y = (float)ol_state->analog[1] / 0x7fff;
if (analog_x <= -axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_LEFT);
if (analog_x >= axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_RIGHT);
if (analog_y <= -axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_UP);
if (analog_y >= axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_DOWN);
analog_x = (float)ol_state->analog[2] / 0x7fff;
analog_y = (float)ol_state->analog[3] / 0x7fff;
if (analog_x <= -axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_Y);
if (analog_x >= axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_A);
if (analog_y <= -axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_X);
if (analog_y >= axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_B);
break;
}
default:
break;
}
button_pressed = input_overlay_add_inputs(ol, ol_state, input_st,
settings,
(input_overlay_show_inputs == OVERLAY_SHOW_INPUT_TOUCHED),
input_overlay_show_inputs_port);
/* Block other touchscreen input as needed. */
if ( button_pressed
#ifdef IOS
|| (ptr_state->device_mask & (1 << RETRO_DEVICE_LIGHTGUN))
|| (ol->flags & INPUT_OVERLAY_BLOCKED))
#else
|| ol_ptr_enable)
#endif
input_st->flags |= INP_FLAG_BLOCK_POINTER_INPUT;
else
input_st->flags &= ~INP_FLAG_BLOCK_POINTER_INPUT;
if (input_overlay_show_inputs == OVERLAY_SHOW_INPUT_NONE)
button_pressed = false;
if (button_pressed || ol_state->touch_count)
input_overlay_post_poll(overlay_visibility, ol,
button_pressed, opacity);
else
input_overlay_poll_clear(overlay_visibility, ol, opacity);
/* Create haptic feedback for any change in button/key state,
* unless touch_count decreased. */
if ( input->keypress_vibrate
&& settings->bools.vibrate_on_keypress
&& ol_state->touch_count
&& ol_state->touch_count >= old_ol_state.touch_count)
{
if ( osk_state_changed
|| bits_any_different(
ol_state->buttons.data,
old_ol_state.buttons.data,
ARRAY_SIZE(old_ol_state.buttons.data))
)
input->keypress_vibrate();
}
old_hitbox_touch_mask = hitbox_touch_mask;
old_ptrdev_touch_mask = ptrdev_touch_mask;
old_blocked_touch_idx = blocked_touch_idx;
ptr_state->device_mask = 0;
}
#endif
/**
* input_config_translate_str_to_rk:
* @str : String to translate to key ID.
*
* Translates string representation to key identifier.
*
* Returns: key identifier.
**/
enum retro_key input_config_translate_str_to_rk(const char *str, size_t len)
{
size_t i;
if (len == 1 && ISALPHA((int)*str))
return (enum retro_key)(RETROK_a + (TOLOWER((int)*str) - (int)'a'));
for (i = 0; input_config_key_map[i].str; i++)
{
if (string_is_equal_noncase(input_config_key_map[i].str, str))
return input_config_key_map[i].key;
}
return RETROK_UNKNOWN;
}
/**
* input_config_translate_str_to_bind_id:
* @str : String to translate to bind ID.
*
* Translate string representation to bind ID.
*
* Returns: Bind ID value on success, otherwise
* RARCH_BIND_LIST_END on not found.
**/
unsigned input_config_translate_str_to_bind_id(const char *str)
{
unsigned i;
for (i = 0; input_config_bind_map[i].valid; i++)
if (string_is_equal(str, input_config_bind_map[i].base))
return i;
return RARCH_BIND_LIST_END;
}
size_t input_config_get_bind_string(
void *settings_data,
char *s,
const struct retro_keybind *bind,
const struct retro_keybind *auto_bind,
size_t len)
{
settings_t *settings = (settings_t*)settings_data;
size_t _len = 0;
int delim = 0;
bool input_descriptor_label_show =
settings->bools.input_descriptor_label_show;
*s = '\0';
if (bind && bind->joykey != NO_BTN)
_len = input_config_get_bind_string_joykey(
input_descriptor_label_show,
s, "", bind, len);
else if (bind && bind->joyaxis != AXIS_NONE)
_len = input_config_get_bind_string_joyaxis(
input_descriptor_label_show,
s, "", bind, len);
else if (auto_bind && auto_bind->joykey != NO_BTN)
_len = input_config_get_bind_string_joykey(
input_descriptor_label_show,
s, "(Auto)", auto_bind, len);
else if (auto_bind && auto_bind->joyaxis != AXIS_NONE)
_len = input_config_get_bind_string_joyaxis(
input_descriptor_label_show,
s, "(Auto)", auto_bind, len);
if (*s)
delim = 1;
#ifndef RARCH_CONSOLE
{
char key[64];
key[0] = '\0';
input_keymaps_translate_rk_to_str(bind->key, key, sizeof(key));
if ( key[0] == 'n'
&& key[1] == 'u'
&& key[2] == 'l'
&& key[3] == '\0'
)
*key = '\0';
/*empty?*/
else if (*key != '\0')
{
if (delim)
_len += strlcpy(s + _len, ", ", len - _len);
_len += snprintf(s + _len, len - _len,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_INPUT_KEY), key);
delim = 1;
}
}
#endif
if (bind->mbutton != NO_BTN)
{
int tag = 0;
switch (bind->mbutton)
{
case RETRO_DEVICE_ID_MOUSE_LEFT:
tag = MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_LEFT;
break;
case RETRO_DEVICE_ID_MOUSE_RIGHT:
tag = MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_RIGHT;
break;
case RETRO_DEVICE_ID_MOUSE_MIDDLE:
tag = MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_MIDDLE;
break;
case RETRO_DEVICE_ID_MOUSE_BUTTON_4:
tag = MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_BUTTON4;
break;
case RETRO_DEVICE_ID_MOUSE_BUTTON_5:
tag = MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_BUTTON5;
break;
case RETRO_DEVICE_ID_MOUSE_WHEELUP:
tag = MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_WHEEL_UP;
break;
case RETRO_DEVICE_ID_MOUSE_WHEELDOWN:
tag = MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_WHEEL_DOWN;
break;
case RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP:
tag = MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_HORIZ_WHEEL_UP;
break;
case RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN:
tag = MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_HORIZ_WHEEL_DOWN;
break;
}
if (tag != 0)
{
if (delim)
_len += strlcpy(s + _len, ", ", len - _len);
_len += strlcpy(s + _len, msg_hash_to_str((enum msg_hash_enums)tag), len - _len);
}
}
/*completely empty?*/
if (*s == '\0')
_len += strlcpy(s + _len, RARCH_NO_BIND, len - _len);
return _len;
}
size_t input_config_get_bind_string_joykey(
bool input_descriptor_label_show,
char *s, const char *suffix,
const struct retro_keybind *bind, size_t len)
{
size_t _len = 0;
if (GET_HAT_DIR(bind->joykey))
{
if ( bind->joykey_label
&& (bind->joykey_label && *bind->joykey_label)
&& input_descriptor_label_show)
return fill_pathname_join_delim(s,
bind->joykey_label, suffix, ' ', len);
/* TODO/FIXME - localize */
_len = snprintf(s, len,
"Hat #%u ", (unsigned)GET_HAT(bind->joykey));
switch (GET_HAT_DIR(bind->joykey))
{
case HAT_UP_MASK:
_len += strlcpy(s + _len, "Up", len - _len);
break;
case HAT_DOWN_MASK:
_len += strlcpy(s + _len, "Down", len - _len);
break;
case HAT_LEFT_MASK:
_len += strlcpy(s + _len, "Left", len - _len);
break;
case HAT_RIGHT_MASK:
_len += strlcpy(s + _len, "Right", len - _len);
break;
default:
_len += strlcpy(s + _len, "?", len - _len);
break;
}
}
else
{
if ( bind->joykey_label
&& (bind->joykey_label && *bind->joykey_label)
&& input_descriptor_label_show)
return fill_pathname_join_delim(s,
bind->joykey_label, suffix, ' ', len);
/* TODO/FIXME - localize */
_len = strlcpy(s, "Button ", len);
_len += snprintf(s + _len, len - _len, "%u",
(unsigned)bind->joykey);
}
if (suffix && *suffix)
_len += snprintf(s + _len, len - _len, " %s", suffix);
return _len;
}
size_t input_config_get_bind_string_joyaxis(
bool input_descriptor_label_show,
char *s, const char *suffix,
const struct retro_keybind *bind, size_t len)
{
size_t _len = 0;
if ( bind->joyaxis_label
&& (bind->joyaxis_label && *bind->joyaxis_label)
&& input_descriptor_label_show)
return fill_pathname_join_delim(s,
bind->joyaxis_label, suffix, ' ', len);
/* TODO/FIXME - localize */
_len = strlcpy(s, "Axis ", len);
if (AXIS_NEG_GET(bind->joyaxis) != AXIS_DIR_NONE)
_len += snprintf(s + _len, len - _len, "-%u",
(unsigned)AXIS_NEG_GET(bind->joyaxis));
else if (AXIS_POS_GET(bind->joyaxis) != AXIS_DIR_NONE)
_len += snprintf(s + _len, len - _len, "+%u",
(unsigned)AXIS_POS_GET(bind->joyaxis));
if (suffix && *suffix)
_len += snprintf(s + _len, len - _len, " %s", suffix);
return _len;
}
void osk_update_last_codepoint(
unsigned *last_codepoint,
unsigned *last_codepoint_len,
const char *word)
{
const char *letter = word;
const char *pos = letter;
if (word[0] == 0)
{
*last_codepoint = 0;
*last_codepoint_len = 0;
return;
}
for (;;)
{
unsigned codepoint = utf8_walk(&letter);
if (letter[0] == 0)
{
*last_codepoint = codepoint;
*last_codepoint_len = (unsigned)(letter - pos);
break;
}
pos = letter;
}
}
#ifdef HAVE_LANGEXTRA
/* combine 3 korean elements. make utf8 character */
static unsigned get_kr_utf8(int c1, int c2, int c3)
{
int uv = c1 * (28 * 21) + c2 * 28 + c3 + 0xac00;
int tv = (uv >> 12) | ((uv & 0x0f00) << 2) | ((uv & 0xc0) << 2) | ((uv & 0x3f) << 16);
return (tv | 0x8080e0);
}
/* utf8 korean composition */
static unsigned get_kr_composition(char* pcur, char* padd)
{
size_t _len;
static char cc1[] = {"ㄱㄱㄲ ㄷㄷㄸ ㅂㅂㅃ ㅅㅅㅆ ㅈㅈㅉ"};
static char cc2[] = {"ㅗㅏㅘ ㅗㅐㅙ ㅗㅣㅚ ㅜㅓㅝ ㅜㅔㅞ ㅜㅣㅟ ㅡㅣㅢ"};
static char cc3[] = {"ㄱㄱㄲ ㄱㅅㄳ ㄴㅈㄵ ㄴㅎㄶ ㄹㄱㄺ ㄹㅁㄻ ㄹㅂㄼ ㄹㅅㄽ ㄹㅌㄾ ㄹㅍㄿ ㄹㅎㅀ ㅂㅅㅄ ㅅㅅㅆ"};
static char s1[] = {"ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣㆍㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ"};
char *tmp1 = NULL;
char *tmp2 = NULL;
int c1 = -1;
int c2 = -1;
int c3 = 0;
int nv = -1;
char utf8[8] = {0, 0, 0, 0, 0, 0, 0, 0};
unsigned ret = *((unsigned*)pcur);
/* check korean */
if (!pcur[0] || !pcur[1] || !pcur[2] || pcur[3])
return ret;
if (!padd[0] || !padd[1] || !padd[2] || padd[3])
return ret;
if ((tmp1 = strstr(s1, pcur)))
c1 = (int)((tmp1 - s1) / 3);
if ((tmp1 = strstr(s1, padd)))
nv = (int)((tmp1 - s1) / 3);
if (nv == -1 || nv >= 19 + 21)
return ret;
/* single element composition */
_len = strlcpy(utf8, pcur, sizeof(utf8));
strlcpy(utf8 + _len, padd, sizeof(utf8) - _len);
if ((tmp2 = strstr(cc1, utf8)))
{
*((unsigned*)padd) = *((unsigned*)(tmp2 + 6)) & 0xffffff;
return 0;
}
else if ((tmp2 = strstr(cc2, utf8)))
{
*((unsigned*)padd) = *((unsigned*)(tmp2 + 6)) & 0xffffff;
return 0;
}
if (tmp2 && tmp2 < cc2 + sizeof(cc2) - 10)
{
*((unsigned*)padd) = *((unsigned*)(tmp2 + 6)) & 0xffffff;
return 0;
}
if (c1 >= 19)
return ret;
if (c1 == -1)
{
int tv = ((pcur[0] & 0x0f) << 12) | ((pcur[1] & 0x3f) << 6) | (pcur[2] & 0x03f);
tv = tv - 0xac00;
c1 = tv / (28 * 21);
c2 = (tv % (28 * 21)) / 28;
c3 = (tv % (28));
if (c1 < 0 || c1 >= 19 || c2 < 0 || c2 > 21 || c3 < 0 || c3 > 28)
return ret;
}
if (c1 == -1 && c2 == -1 && c3 == 0)
return ret;
if (c2 == -1 && c3 == 0)
{
/* 2nd element attach */
if (nv < 19)
return ret;
c2 = nv - 19;
}
else
if (c2 >= 0 && c3 == 0)
{
if (nv < 19)
{
/* 3rd element attach */
if (!(tmp1 = strstr(s1 + (19 + 21) * 3, padd)))
return ret;
c3 = (int)((tmp1 - s1) / 3 - 19 - 21);
}
else
{
/* 2nd element transform */
strlcpy(utf8, s1 + (19 + c2) * 3, 4);
utf8[3] = 0;
strlcpy(utf8 + 3, padd, sizeof(utf8) - 3);
if ( !(tmp2 = strstr(cc2, utf8))
|| (tmp2 >= cc2 + sizeof(cc2) - 10))
return ret;
strlcpy(utf8, tmp2 + 6, 4);
utf8[3] = 0;
if (!(tmp1 = strstr(s1 + (19) * 3, utf8)))
return ret;
c2 = (int)((tmp1 - s1) / 3 - 19);
}
}
else
if (c3 > 0)
{
strlcpy(utf8, s1 + (19 + 21 + c3) * 3, 4);
utf8[3] = 0;
if (nv < 19)
{
/* 3rd element transform */
strlcpy(utf8 + 3, padd, sizeof(utf8) - 3);
if ( !(tmp2 = strstr(cc3, utf8))
|| (tmp2 >= cc3 + sizeof(cc3) - 10))
return ret;
strlcpy(utf8, tmp2 + 6, 4);
utf8[3] = 0;
if (!(tmp1 = strstr(s1 + (19 + 21) * 3, utf8)))
return ret;
c3 = (int)((tmp1 - s1) / 3 - 19 - 21);
}
else
{
int tv = 0;
if ((tmp2 = strstr(cc3, utf8)))
tv = (tmp2 - cc3) % 10;
if (tv == 6)
{
/* complex 3rd element -> disassemble */
strlcpy(utf8, tmp2 - 3, 4);
if (!(tmp1 = strstr(s1, utf8)))
return ret;
tv = (int)((tmp1 - s1) / 3);
strlcpy(utf8, tmp2 - 6, 4);
if (!(tmp1 = strstr(s1 + (19 + 21) * 3, utf8)))
return ret;
c3 = (int)((tmp1 - s1) / 3 - 19 - 21);
}
else
{
if (!(tmp1 = strstr(s1, utf8)) || (tmp1 - s1) >= 19 * 3)
return ret;
tv = (int)((tmp1 - s1) / 3);
c3 = 0;
}
*((unsigned*)padd) = get_kr_utf8(tv, nv - 19, 0);
return get_kr_utf8(c1, c2, c3);
}
}
else
return ret;
*((unsigned*)padd) = get_kr_utf8(c1, c2, c3);
return 0;
}
#endif
/**
* input_keyboard_line_event:
* @state : Input keyboard line handle.
* @character : Inputted character.
*
* Called on every keyboard character event.
*
* Returns: true (1) on success, otherwise false (0).
**/
static bool input_keyboard_line_event(
input_driver_state_t *input_st,
input_keyboard_line_t *state, uint32_t character)
{
char array[2];
bool ret = false;
const char *word = NULL;
char c = (character >= 128) ? '?' : character;
#ifdef HAVE_LANGEXTRA
static uint32_t composition = 0;
/* reset composition, when edit box is opened. */
if (state->size == 0)
composition = 0;
/* reset composition, when 1 byte(=english) input */
if (character && character < 0xff)
composition = 0;
if (IS_COMPOSITION(character) || IS_END_COMPOSITION(character))
{
size_t _len = strlen((char*)&composition);
if (composition && state->buffer && state->size >= _len && state->ptr >= _len)
{
memmove(state->buffer + state->ptr - _len, state->buffer + state->ptr, _len + 1);
state->ptr -= _len;
state->size -= _len;
}
if (IS_COMPOSITION_KR(character) && composition)
{
unsigned new_comp;
character = character & 0xffffff;
new_comp = get_kr_composition((char*)&composition, (char*)&character);
if (new_comp)
input_keyboard_line_append(state, (char*)&new_comp, 3);
composition = character;
}
else
{
if (IS_END_COMPOSITION(character))
composition = 0;
else
composition = character & 0xffffff;
character &= 0xffffff;
}
if (character)
input_keyboard_line_append(state, (char*)&character, strlen((char*)&character));
word = state->buffer;
}
else
#endif
/* Treat extended chars as ? as we cannot support
* printable characters for unicode stuff. */
if (c == '\r' || c == '\n')
{
state->cb(state->userdata, state->buffer);
array[0] = c;
array[1] = 0;
ret = true;
word = array;
}
else if (c == '\b' || c == '\x7f') /* 0x7f is ASCII for del */
{
if (state->ptr && state->buffer)
{
size_t ptr = state->ptr;
while (state->ptr)
{
state->ptr--;
if (!IS_UTF8_CONTINUATION(state->buffer[state->ptr]))
break;
}
memmove(state->buffer + state->ptr,
state->buffer + ptr,
state->size - ptr + 1);
state->size -= ptr - state->ptr;
word = state->buffer;
}
}
else if (ISPRINT(c))
{
char *newbuf = (char*)
realloc(state->buffer, state->size + 2);
if (!newbuf)
return false;
memmove(newbuf + state->ptr + 1,
newbuf + state->ptr,
state->size - state->ptr + 1);
newbuf[state->ptr] = c;
state->ptr++;
state->size++;
newbuf[state->size] = '\0';
state->buffer = newbuf;
array[0] = c;
array[1] = 0;
word = array;
}
/* OSK - update last character */
if (word)
osk_update_last_codepoint(
&input_st->osk_last_codepoint,
&input_st->osk_last_codepoint_len,
word);
return ret;
}
void input_event_osk_append(
input_keyboard_line_t *keyboard_line,
enum osk_type *osk_idx,
unsigned *osk_last_codepoint,
unsigned *osk_last_codepoint_len,
int ptr,
bool show_symbol_pages,
const char *word,
size_t len)
{
#ifdef HAVE_LANGEXTRA
if (memcmp(word, "\xe2\x87\xa6", 4) == 0) /* backspace character */
input_keyboard_event(true, '\x7f', '\x7f', 0, RETRO_DEVICE_KEYBOARD);
else if (memcmp(word, "\xe2\x8f\x8e", 4) == 0) /* return character */
input_keyboard_event(true, '\n', '\n', 0, RETRO_DEVICE_KEYBOARD);
else if (memcmp(word, "\xe2\x87\xa7", 4) == 0) /* up arrow */
*osk_idx = OSK_UPPERCASE_LATIN;
else if (memcmp(word, "\xe2\x87\xa9", 4) == 0) /* down arrow */
*osk_idx = OSK_LOWERCASE_LATIN;
else if (memcmp(word, "\xe2\x8a\x95", 4) == 0) /* plus sign (next button) */
{
if (*msg_hash_get_uint(MSG_HASH_USER_LANGUAGE) == RETRO_LANGUAGE_KOREAN)
{
static int prv_osk = OSK_TYPE_UNKNOWN + 1;
if (*osk_idx < OSK_KOREAN_PAGE1)
{
prv_osk = *osk_idx;
*osk_idx = OSK_KOREAN_PAGE1;
}
else
*osk_idx = (enum osk_type)prv_osk;
}
else if (*osk_idx < (show_symbol_pages ? OSK_TYPE_LAST - 1 : OSK_SYMBOLS_PAGE1))
*osk_idx = (enum osk_type)(*osk_idx + 1);
else
*osk_idx = (enum osk_type)(OSK_TYPE_UNKNOWN + 1);
}
else if (*osk_idx == OSK_KOREAN_PAGE1 && word && len == 3)
{
unsigned character = *((unsigned*)word) | 0x01000000;
input_keyboard_line_event(&input_driver_st, keyboard_line, character);
}
#else
if (memcmp(word, "Bksp", 5) == 0)
input_keyboard_event(true, '\x7f', '\x7f', 0, RETRO_DEVICE_KEYBOARD);
else if (memcmp(word, "Enter", 6) == 0)
input_keyboard_event(true, '\n', '\n', 0, RETRO_DEVICE_KEYBOARD);
else if (memcmp(word, "Upper", 6) == 0)
*osk_idx = OSK_UPPERCASE_LATIN;
else if (memcmp(word, "Lower", 6) == 0)
*osk_idx = OSK_LOWERCASE_LATIN;
else if (memcmp(word, "Next", 5) == 0)
{
if (*osk_idx < (show_symbol_pages ? OSK_TYPE_LAST - 1 : OSK_SYMBOLS_PAGE1))
*osk_idx = (enum osk_type)(*osk_idx + 1);
else
*osk_idx = (enum osk_type)(OSK_TYPE_UNKNOWN + 1);
}
#endif
else
{
input_keyboard_line_append(keyboard_line, word, len);
osk_update_last_codepoint(
osk_last_codepoint,
osk_last_codepoint_len,
word);
}
}
void *input_driver_init_wrap(input_driver_t *input, const char *name)
{
void *ret = NULL;
if (!input || !input->init)
return NULL;
if ((ret = input->init(name)))
{
input_driver_init_joypads();
return ret;
}
return NULL;
}
bool input_driver_find_driver(settings_t *settings,
const char *prefix, bool verbosity_enabled)
{
int i = (int)driver_find_index("input_driver",
settings->arrays.input_driver);
if (i >= 0)
{
input_driver_st.current_driver = (input_driver_t*)input_drivers[i];
RARCH_LOG("[Input] Found %s: \"%s\".\n", prefix,
input_driver_st.current_driver->ident);
}
else
{
input_driver_t *tmp = NULL;
if (verbosity_enabled)
{
unsigned d;
RARCH_ERR("Couldn't find any %s named \"%s\"\n", prefix,
settings->arrays.input_driver);
RARCH_LOG_OUTPUT("Available %ss are:\n", prefix);
for (d = 0; input_drivers[d]; d++)
RARCH_LOG_OUTPUT("\t%s\n", input_drivers[d]->ident);
RARCH_WARN("Going to default to first %s...\n", prefix);
}
tmp = (input_driver_t*)input_drivers[0];
if (!tmp)
return false;
input_driver_st.current_driver = tmp;
}
return true;
}
void input_mapper_reset(void *data)
{
unsigned i;
input_mapper_t *handle = (input_mapper_t*)data;
for (i = 0; i < MAX_USERS; i++)
{
unsigned j;
for (j = 0; j < 8; j++)
{
handle->analog_value[i][j] = 0;
handle->buttons[i].data[j] = 0;
handle->buttons[i].analogs[j] = 0;
handle->buttons[i].analog_buttons[j] = 0;
}
}
for (i = 0; i < RETROK_LAST; i++)
handle->key_button[i] = 0;
for (i = 0; i < (RETROK_LAST / 32 + 1); i++)
handle->keys[i] = 0;
}
/**
* Sets the sensor state. Used by RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE.
*
* @param port
* @param action
* @param rate
*
* @return true if the sensor state has been successfully set
**/
bool input_set_sensor_state(unsigned port,
enum retro_sensor_action action, unsigned rate)
{
settings_t *settings = config_get_ptr();
/* Default to enabled if settings not loaded yet (matches config default) */
bool input_sensors_enable = settings
? settings->bools.input_sensors_enable
: true;
unsigned joy_idx = settings
? settings->uints.input_joypad_index[port]
: port;
return input_driver_set_sensor(
joy_idx, input_sensors_enable, action, rate);
}
/* Core-facing wrappers that track which sensors the core has enabled.
* Used as the retro_sensor_interface callbacks so the frontend knows
* what the core requested independently of shader/platform enables. */
bool input_core_set_sensor_state(unsigned port,
enum retro_sensor_action action, unsigned rate)
{
input_driver_state_t *input_st = &input_driver_st;
switch (action)
{
case RETRO_SENSOR_ACCELEROMETER_ENABLE:
input_st->core_accel_rate = rate;
break;
case RETRO_SENSOR_ACCELEROMETER_DISABLE:
input_st->core_accel_rate = 0;
break;
case RETRO_SENSOR_GYROSCOPE_ENABLE:
input_st->core_gyro_rate = rate;
break;
case RETRO_SENSOR_GYROSCOPE_DISABLE:
input_st->core_gyro_rate = 0;
break;
default:
break;
}
/* For accel/gyro, the poll loop reconciles core/shader/frontend
* state and manages driver enable/disable. For other sensor types,
* pass through to the driver directly. */
switch (action)
{
case RETRO_SENSOR_ACCELEROMETER_ENABLE:
case RETRO_SENSOR_ACCELEROMETER_DISABLE:
case RETRO_SENSOR_GYROSCOPE_ENABLE:
case RETRO_SENSOR_GYROSCOPE_DISABLE:
return true;
default:
return input_set_sensor_state(port, action, rate);
}
}
float input_core_get_sensor_state(unsigned port, unsigned id)
{
input_driver_state_t *input_st = &input_driver_st;
/* Only return data for sensor types the core has enabled */
if (id <= RETRO_SENSOR_ACCELEROMETER_Z && !input_st->core_accel_rate)
return 0.0f;
if ( id >= RETRO_SENSOR_GYROSCOPE_X
&& id <= RETRO_SENSOR_GYROSCOPE_Z
&& !input_st->core_gyro_rate)
return 0.0f;
return input_get_sensor_state(port, id);
}
const char *joypad_driver_name(unsigned i)
{
if (!input_driver_st.primary_joypad || !input_driver_st.primary_joypad->name)
return NULL;
return input_driver_st.primary_joypad->name(i);
}
void joypad_driver_reinit(void *data, const char *joypad_driver_name)
{
if (input_driver_st.primary_joypad)
{
const input_device_driver_t *tmp = input_driver_st.primary_joypad;
input_driver_st.primary_joypad = NULL;
/* Run poll one last time in order to detect disconnections */
tmp->poll();
tmp->destroy();
}
#ifdef HAVE_MFI
if (input_driver_st.secondary_joypad)
{
const input_device_driver_t *tmp = input_driver_st.secondary_joypad;
input_driver_st.secondary_joypad = NULL;
tmp->poll();
tmp->destroy();
}
#endif
if (!input_driver_st.primary_joypad)
input_driver_st.primary_joypad = input_joypad_init_driver(joypad_driver_name, data);
}
/**
* Retrieves the sensor state associated with the provided port and ID.
*
* @param port
* @param id Sensor ID
*
* @return The current state associated with the port and ID as a float
**/
/**
* Internal version that accepts a pre-resolved settings pointer,
* avoiding repeated config_get_ptr() calls when invoked in a batch
* (e.g. the per-frame sensor cache update in input_driver_poll).
*/
static float input_get_sensor_state_internal(
settings_t *settings, unsigned port, unsigned id)
{
float raw_value;
unsigned joy_idx;
bool input_sensors_enable = false;
float sensitivity = 1.0f;
unsigned fetch_id = id;
float sign = 1.0f;
/* Return 0 if settings unavailable (e.g., during driver transitions) */
if (!settings)
return 0.0f;
if (port >= MAX_USERS)
return 0.0f;
input_sensors_enable = settings->bools.input_sensors_enable;
joy_idx = settings->uints.input_joypad_index[port];
/* Return 0 when sensors disabled */
if (!input_sensors_enable)
return 0.0f;
/* Apply sensor axis remap from autoconfig profile */
if (id <= RETRO_SENSOR_GYROSCOPE_Z)
{
const input_sensor_map_t *map = input_config_get_sensor_map(joy_idx);
if (map && map->axes[id].source >= 0)
{
fetch_id = (unsigned)map->axes[id].source;
sign = (float)map->axes[id].sign;
}
}
/* Get sensitivity for sensor type */
if (id >= RETRO_SENSOR_ACCELEROMETER_X && id <= RETRO_SENSOR_ACCELEROMETER_Z)
sensitivity = settings->floats.input_sensor_accelerometer_sensitivity;
else if (id >= RETRO_SENSOR_GYROSCOPE_X && id <= RETRO_SENSOR_GYROSCOPE_Z)
sensitivity = settings->floats.input_sensor_gyroscope_sensitivity;
/* Apply sensor orientation rotation for X and Y axes.
* Skip on iOS/tvOS — cocoa_input handles rotation at the driver level.
* Setting values: 0=Auto, 1=0°, 2=90°, 3=180°, 4=270°
* Rotation transforms (clockwise):
* 0°: (x, y) -> (x, y)
* 90°: (x, y) -> (y, -x)
* 180°: (x, y) -> (-x, -y)
* 270°: (x, y) -> (-y, x)
* Both accelerometer and gyroscope use the same orientation setting
* since they share the same physical sensor orientation */
#ifndef HAVE_COCOATOUCH
if (id == RETRO_SENSOR_ACCELEROMETER_X || id == RETRO_SENSOR_ACCELEROMETER_Y ||
id == RETRO_SENSOR_GYROSCOPE_X || id == RETRO_SENSOR_GYROSCOPE_Y)
{
unsigned orientation_setting = settings->uints.input_sensor_orientation;
unsigned rotation = 0;
bool is_gyro = (id == RETRO_SENSOR_GYROSCOPE_X || id == RETRO_SENSOR_GYROSCOPE_Y);
bool is_x_axis = (id == RETRO_SENSOR_ACCELEROMETER_X || id == RETRO_SENSOR_GYROSCOPE_X);
if (orientation_setting == 0)
{
/* Auto: detect from platform */
#ifdef ANDROID
if (g_android)
rotation = g_android->detected_screen_rotation;
#endif
}
else
{
/* Manual: setting 1=0°, 2=90°, 3=180°, 4=270° */
rotation = orientation_setting - 1;
}
switch (rotation)
{
case 1: /* 90° CW: X->Y, Y->-X */
if (is_x_axis)
fetch_id = is_gyro ? RETRO_SENSOR_GYROSCOPE_Y : RETRO_SENSOR_ACCELEROMETER_Y;
else
{
fetch_id = is_gyro ? RETRO_SENSOR_GYROSCOPE_X : RETRO_SENSOR_ACCELEROMETER_X;
sign *= -1.0f;
}
break;
case 2: /* 180°: X->-X, Y->-Y */
sign *= -1.0f;
break;
case 3: /* 270° CW: X->-Y, Y->X */
if (is_x_axis)
{
fetch_id = is_gyro ? RETRO_SENSOR_GYROSCOPE_Y : RETRO_SENSOR_ACCELEROMETER_Y;
sign *= -1.0f;
}
else
fetch_id = is_gyro ? RETRO_SENSOR_GYROSCOPE_X : RETRO_SENSOR_ACCELEROMETER_X;
break;
default: /* 0°: no change */
break;
}
}
#endif /* HAVE_COCOATOUCH */
/* Get raw sensor value (use fetch_id for rotated axes) */
raw_value = input_driver_get_sensor(joy_idx, true, fetch_id);
/* Apply sensitivity and rotation sign */
return raw_value * sensitivity * sign;
}
float input_get_sensor_state(unsigned port, unsigned id)
{
return input_get_sensor_state_internal(config_get_ptr(), port, id);
}
void input_sensor_start_rest_capture(void)
{
input_driver_state_t *input_st = &input_driver_st;
input_st->rest_accum[0] = 0.0f;
input_st->rest_accum[1] = 0.0f;
input_st->rest_accum[2] = 0.0f;
input_st->rest_sample_count = 0;
input_st->rest_capturing = true;
}
static void input_sensor_update_rest_capture(settings_t *settings)
{
input_driver_state_t *input_st = &input_driver_st;
if (!input_st->rest_capturing)
return;
input_st->rest_accum[0] += input_get_sensor_state_internal(settings, 0, RETRO_SENSOR_ACCELEROMETER_X);
input_st->rest_accum[1] += input_get_sensor_state_internal(settings, 0, RETRO_SENSOR_ACCELEROMETER_Y);
input_st->rest_accum[2] += input_get_sensor_state_internal(settings, 0, RETRO_SENSOR_ACCELEROMETER_Z);
input_st->rest_sample_count++;
if (input_st->rest_sample_count >= 30)
{
input_st->sensor_accelerometer_rest[0] = input_st->rest_accum[0] / 30.0f;
input_st->sensor_accelerometer_rest[1] = input_st->rest_accum[1] / 30.0f;
input_st->sensor_accelerometer_rest[2] = input_st->rest_accum[2] / 30.0f;
input_st->rest_capturing = false;
}
}
/**
* Sets the rumble state. Used by RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE.
*
* @param port User number.
* @param effect Rumble effect.
* @param strength Strength of rumble effect.
*
* @return true if the rumble state has been successfully set
**/
bool input_set_rumble_state(unsigned port,
enum retro_rumble_effect effect, uint16_t strength)
{
settings_t *settings = config_get_ptr();
unsigned joy_idx = settings->uints.input_joypad_index[port];
uint16_t scaled_strength = strength;
/* If gain setting is not supported, do software gain control */
if (input_driver_st.primary_joypad)
{
if (!input_driver_st.primary_joypad->set_rumble_gain)
{
unsigned rumble_gain = settings->uints.input_rumble_gain;
scaled_strength = (rumble_gain * strength) / 100.0;
}
}
return input_driver_set_rumble(
port, joy_idx, effect, scaled_strength);
}
/**
* Sets the rumble gain. Used by MENU_ENUM_LABEL_INPUT_RUMBLE_GAIN.
*
* @param gain Rumble gain, 0-100 [%]
*
* @return true if the rumble gain has been successfully set
**/
bool input_set_rumble_gain(unsigned gain)
{
return (input_driver_set_rumble_gain(
gain, config_get_ptr()->uints.input_max_users));
}
uint64_t input_driver_get_capabilities(void)
{
const input_driver_t *input = input_driver_st.current_driver;
if (!input || !input->get_capabilities)
return 0;
return input->get_capabilities(input_driver_st.current_data);
}
void input_driver_init_joypads(void)
{
settings_t *settings = config_get_ptr();
if (!input_driver_st.primary_joypad)
input_driver_st.primary_joypad = input_joypad_init_driver(
settings->arrays.input_joypad_driver,
input_driver_st.current_data);
}
bool input_key_pressed(int key, bool keyboard_pressed)
{
/* If a keyboard key is pressed then immediately return
* true, otherwise call button_is_pressed to determine
* if the input comes from another input device */
if (!((key < RARCH_BIND_LIST_END) && keyboard_pressed))
{
const input_device_driver_t
*joypad = (const input_device_driver_t*)
input_driver_st.primary_joypad;
const uint64_t bind_joykey = input_config_binds[0][key].joykey;
const uint64_t bind_joyaxis = input_config_binds[0][key].joyaxis;
const uint64_t autobind_joykey = input_autoconf_binds[0][key].joykey;
const uint64_t autobind_joyaxis= input_autoconf_binds[0][key].joyaxis;
uint16_t port = 0;
float axis_threshold = config_get_ptr()->floats.input_axis_threshold;
const uint64_t joykey = (bind_joykey != NO_BTN)
? bind_joykey : autobind_joykey;
const uint64_t joyaxis = (bind_joyaxis != AXIS_NONE)
? bind_joyaxis : autobind_joyaxis;
if ((uint16_t)joykey != NO_BTN && joypad->button(
port, (uint16_t)joykey))
return true;
if (joyaxis != AXIS_NONE &&
((float)abs(joypad->axis(port, (uint32_t)joyaxis))
* INV_0x8000) > axis_threshold)
return true;
return false;
}
return true;
}
bool video_driver_init_input(
input_driver_t *tmp,
settings_t *settings,
bool verbosity_enabled)
{
void *new_data = NULL;
input_driver_t **input = &input_driver_st.current_driver;
if (*input)
#if HAVE_TEST_DRIVERS
/* Test driver not in use, keep selected driver */
if (strcmp(settings->arrays.input_driver, "test") != 0)
return true;
else if (!*settings->paths.test_input_file_general)
{
RARCH_LOG("[Input] Test input driver selected, but no input file provided - falling back.\n");
return true;
}
else
RARCH_LOG("[Video] Graphics driver initialized an input driver, but ignoring it as test input driver is in use.\n");
#else
return true;
#endif
else
/* Video driver didn't provide an input driver,
* so we use configured one. */
RARCH_LOG("[Video] Graphics driver did not initialize an input driver."
" Attempting to pick a suitable driver.\n");
if (tmp)
*input = tmp;
else
{
if (!(input_driver_find_driver(
settings, "input driver",
verbosity_enabled)))
{
RARCH_ERR("[Video] Cannot find input driver. Exiting...\n");
return false;
}
}
/* This should never really happen as tmp (driver.input) is always
* found before this in find_driver_input(), or we have aborted
* in a similar fashion anyways. */
if ( !input_driver_st.current_driver
|| !(new_data = input_driver_init_wrap(
input_driver_st.current_driver,
settings->arrays.input_joypad_driver)))
{
RARCH_ERR("[Video] Cannot initialize input driver. Exiting...\n");
return false;
}
input_driver_st.current_data = new_data;
return true;
}
bool input_driver_grab_mouse(void)
{
const input_driver_t *input = (const input_driver_t*)input_driver_st.current_driver;
if (!input || !input->grab_mouse)
return false;
input->grab_mouse(input_driver_st.current_data, true);
return true;
}
bool input_driver_ungrab_mouse(void)
{
const input_driver_t *input = (const input_driver_t*)
input_driver_st.current_driver;
if (!input || !input->grab_mouse)
return false;
input->grab_mouse(input_driver_st.current_data, false);
return true;
}
void input_config_reset(void)
{
unsigned i;
input_driver_state_t *input_st = &input_driver_st;
memcpy(input_config_binds[0], retro_keybinds_1, sizeof(retro_keybinds_1));
for (i = 1; i < MAX_USERS; i++)
memcpy(input_config_binds[i], retro_keybinds_rest,
sizeof(retro_keybinds_rest));
for (i = 0; i < MAX_USERS; i++)
{
/* Note: Don't use input_config_clear_device_name()
* here, since this will re-index devices each time
* (not required - we are setting all 'name indices'
* to zero manually) */
input_st->input_device_info[i].name[0] = '\0';
input_st->input_device_info[i].display_name[0] = '\0';
input_st->input_device_info[i].config_name[0] = '\0';
input_st->input_device_info[i].joypad_driver[0] = '\0';
input_st->input_device_info[i].vid = 0;
input_st->input_device_info[i].pid = 0;
input_st->input_device_info[i].autoconfigured = false;
input_st->input_device_info[i].name_index = 0;
input_config_reset_autoconfig_binds(i);
input_st->libretro_input_binds[i] = (const retro_keybind_set *)&input_config_binds[i];
}
}
void input_config_set_device(unsigned port, unsigned id)
{
settings_t *settings = config_get_ptr();
if (settings && (port < MAX_USERS))
configuration_set_uint(settings,
settings->uints.input_libretro_device[port], id);
}
unsigned input_config_get_device(unsigned port)
{
settings_t *settings = config_get_ptr();
if (settings && (port < MAX_USERS))
return settings->uints.input_libretro_device[port];
return RETRO_DEVICE_NONE;
}
const struct retro_keybind *input_config_get_bind_auto(
unsigned port, unsigned id)
{
settings_t *settings = config_get_ptr();
unsigned joy_idx = settings->uints.input_joypad_index[port];
if (joy_idx < MAX_USERS)
return &input_autoconf_binds[joy_idx][id];
return NULL;
}
unsigned *input_config_get_device_ptr(unsigned port)
{
settings_t *settings = config_get_ptr();
if (settings && (port < MAX_USERS))
return &settings->uints.input_libretro_device[port];
return NULL;
}
/* Adds an index to devices with the same name,
* so they can be uniquely identified in the
* frontend */
static void input_config_reindex_device_names(input_driver_state_t *input_st)
{
unsigned i, j;
unsigned name_index;
/* Reset device name indices */
for (i = 0; i < MAX_INPUT_DEVICES; i++)
input_st->input_device_info[i].name_index = 0;
/* Scan device names */
for (i = 0; i < MAX_INPUT_DEVICES; i++)
{
const char *device_name = input_config_get_device_name(i);
/* If current device name is empty, or a non-zero
* name index has already been assigned, continue
* to the next device */
if (
(!device_name || !*device_name)
|| input_st->input_device_info[i].name_index != 0)
continue;
/* > Uniquely named devices have a name index
* of 0
* > Devices with the same name have a name
* index starting from 1 */
name_index = 1;
/* Loop over all devices following the current
* selection */
for (j = i + 1; j < MAX_INPUT_DEVICES; j++)
{
const char *next_device_name = input_config_get_device_name(j);
if (!next_device_name || !*next_device_name)
continue;
/* Check if names match */
if (string_is_equal(device_name, next_device_name))
{
/* If this is the first match, set a starting
* index for the current device selection */
if (input_st->input_device_info[i].name_index == 0)
input_st->input_device_info[i].name_index = name_index++;
/* Set name index for the next device
* (will keep incrementing as more matches
* are found) */
input_st->input_device_info[j].name_index = name_index++;
}
}
}
}
const char *input_config_get_device_name(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
if (!*input_st->input_device_info[port].name)
return NULL;
return input_st->input_device_info[port].name;
}
const char *input_config_get_device_display_name(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
if (!*input_st->input_device_info[port].display_name)
return NULL;
return input_st->input_device_info[port].display_name;
}
const char *input_config_get_device_config_name(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
if (!*input_st->input_device_info[port].config_name)
return NULL;
return input_st->input_device_info[port].config_name;
}
const char *input_config_get_device_joypad_driver(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
if (!*input_st->input_device_info[port].joypad_driver)
return NULL;
return input_st->input_device_info[port].joypad_driver;
}
uint16_t input_config_get_device_vid(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
return input_st->input_device_info[port].vid;
}
uint16_t input_config_get_device_pid(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
return input_st->input_device_info[port].pid;
}
bool input_config_get_device_autoconfigured(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
return input_st->input_device_info[port].autoconfigured;
}
unsigned input_config_get_device_name_index(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
return input_st->input_device_info[port].name_index;
}
/* TODO/FIXME: This is required by linuxraw_joypad.c
* and parport_joypad.c. These input drivers should
* be refactored such that this dubious low-level
* access is not required */
char *input_config_get_device_name_ptr(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
return input_st->input_device_info[port].name;
}
size_t input_config_get_device_name_size(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
return sizeof(input_st->input_device_info[port].name);
}
void input_config_set_device_name(unsigned port, const char *name)
{
input_driver_state_t *input_st = &input_driver_st;
if (!name || !*name)
return;
strlcpy(input_st->input_device_info[port].name, name,
sizeof(input_st->input_device_info[port].name));
input_config_reindex_device_names(input_st);
}
void input_config_set_device_display_name(unsigned port, const char *name)
{
input_driver_state_t *input_st = &input_driver_st;
if (name && *name)
strlcpy(input_st->input_device_info[port].display_name, name,
sizeof(input_st->input_device_info[port].display_name));
}
void input_config_set_device_config_name(unsigned port, const char *name)
{
input_driver_state_t *input_st = &input_driver_st;
if (name && *name)
strlcpy(input_st->input_device_info[port].config_name, name,
sizeof(input_st->input_device_info[port].config_name));
}
void input_config_set_device_joypad_driver(unsigned port, const char *driver)
{
input_driver_state_t *input_st = &input_driver_st;
if (driver && *driver)
strlcpy(input_st->input_device_info[port].joypad_driver, driver,
sizeof(input_st->input_device_info[port].joypad_driver));
}
void input_config_set_device_vid(unsigned port, uint16_t vid)
{
input_driver_state_t *input_st = &input_driver_st;
input_st->input_device_info[port].vid = vid;
}
void input_config_set_device_pid(unsigned port, uint16_t pid)
{
input_driver_state_t *input_st = &input_driver_st;
input_st->input_device_info[port].pid = pid;
}
void input_config_set_device_autoconfigured(unsigned port, bool autoconfigured)
{
input_driver_state_t *input_st = &input_driver_st;
input_st->input_device_info[port].autoconfigured = autoconfigured;
}
void input_config_set_device_name_index(unsigned port, unsigned name_index)
{
input_driver_state_t *input_st = &input_driver_st;
input_st->input_device_info[port].name_index = name_index;
}
void input_config_clear_device_name(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
input_st->input_device_info[port].name[0] = '\0';
input_config_reindex_device_names(input_st);
}
void input_config_clear_device_display_name(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
input_st->input_device_info[port].display_name[0] = '\0';
}
void input_config_clear_device_config_name(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
input_st->input_device_info[port].config_name[0] = '\0';
}
void input_config_clear_device_joypad_driver(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
input_st->input_device_info[port].joypad_driver[0] = '\0';
}
const char *input_config_get_mouse_display_name(unsigned port)
{
input_driver_state_t *input_st = &input_driver_st;
if (!*input_st->input_mouse_info[port].display_name)
return NULL;
return input_st->input_mouse_info[port].display_name;
}
void input_config_set_mouse_display_name(unsigned port, const char *name)
{
char name_ascii[NAME_MAX_LENGTH];
input_driver_state_t *input_st = &input_driver_st;
name_ascii[0] = '\0';
/* Strip non-ASCII characters */
if (name && *name)
{
string_copy_only_ascii(name_ascii, name);
string_trim_whitespace(name_ascii);
}
if (*name_ascii)
strlcpy(input_st->input_mouse_info[port].display_name, name_ascii,
sizeof(input_st->input_mouse_info[port].display_name));
}
void input_keyboard_mapping_bits(unsigned mode, unsigned key)
{
input_driver_state_t *input_st = &input_driver_st;
switch (mode)
{
case 0:
BIT512_CLEAR_PTR(&input_st->keyboard_mapping_bits, key);
break;
case 1:
BIT512_SET_PTR(&input_st->keyboard_mapping_bits, key);
break;
default:
break;
}
}
void config_read_keybinds_conf(void *data)
{
unsigned i;
config_file_t *conf = (config_file_t*)data;
bool key_store[RETROK_LAST] = {0};
if (!conf)
return;
for (i = 0; i < MAX_USERS; i++)
{
unsigned j;
for (j = 0; input_config_bind_map_get_valid(j); j++)
{
char str[NAME_MAX_LENGTH];
char prefix[16];
const struct input_bind_map *keybind =
(const struct input_bind_map*)INPUT_CONFIG_BIND_MAP_GET(j);
struct retro_keybind *bind = &input_config_binds[i][j];
bool meta = false;
const char *btn = NULL;
struct config_entry_list *entry = NULL;
if (!bind || !bind->valid || !keybind || !keybind->valid)
continue;
meta = keybind->meta;
btn = keybind->base;
prefix[0] = '\0';
input_config_get_prefix(prefix, sizeof(prefix), i, meta);
if (!*prefix || !btn)
continue;
fill_pathname_join_delim(str, prefix, btn, '_', sizeof(str));
/* Clear old mapping bit unless just recently set */
if (!key_store[bind->key])
input_keyboard_mapping_bits(0, bind->key);
entry = config_get_entry(conf, str);
if (entry && entry->value && *entry->value)
bind->key = input_config_translate_str_to_rk(
entry->value, strlen(entry->value));
/* Store new mapping bit and remember it for a while
* so that next clear leaves the new key alone */
input_keyboard_mapping_bits(1, bind->key);
key_store[bind->key] = true;
input_config_parse_joy_button (str, conf, prefix, btn, bind);
input_config_parse_joy_axis (str, conf, prefix, btn, bind);
input_config_parse_mouse_button(str, conf, prefix, btn, bind);
}
}
}
#ifdef HAVE_COMMAND
void input_driver_init_command(input_driver_state_t *input_st,
settings_t *settings)
{
#ifdef HAVE_STDIN_CMD
bool input_stdin_cmd_enable = settings->bools.stdin_cmd_enable;
if (input_stdin_cmd_enable)
{
input_driver_state_t *input_st = &input_driver_st;
const input_driver_t *input = (const input_driver_t*)
input_st->current_driver;
bool grab_stdin = input->grab_stdin
&& input->grab_stdin(input_st->current_data);
if (grab_stdin)
{
RARCH_WARN("stdin command interface is desired, "
"but input driver has already claimed stdin.\n"
"Cannot use this command interface.\n");
}
else
{
input_st->command[0] = command_stdin_new();
if (!input_st->command[0])
RARCH_ERR("Failed to initialize the stdin command interface.\n");
}
}
#endif
/* Initialize the network command interface */
#ifdef HAVE_NETWORK_CMD
{
bool input_network_cmd_enable = settings->bools.network_cmd_enable;
if (input_network_cmd_enable)
{
unsigned network_cmd_port = settings->uints.network_cmd_port;
if (!(input_st->command[1] = command_network_new(network_cmd_port)))
RARCH_ERR("Failed to initialize the network command interface.\n");
}
}
#endif
#if defined(HAVE_LAKKA)
if (!(input_st->command[2] = command_uds_new()))
RARCH_ERR("Failed to initialize the UDS command interface.\n");
#elif defined(EMSCRIPTEN)
if (!(input_st->command[2] = command_emscripten_new()))
RARCH_ERR("Failed to initialize the emscripten command interface.\n");
#endif
}
void input_driver_deinit_command(input_driver_state_t *input_st)
{
int i;
for (i = 0; i < (int)ARRAY_SIZE(input_st->command); i++)
{
if (input_st->command[i])
input_st->command[i]->destroy(
input_st->command[i]);
input_st->command[i] = NULL;
}
}
#endif
void input_game_focus_free(void)
{
input_game_focus_state_t *game_focus_st = &input_driver_st.game_focus_state;
/* Ensure that game focus mode is disabled */
if (game_focus_st->enabled)
{
enum input_game_focus_cmd_type game_focus_cmd = GAME_FOCUS_CMD_OFF;
command_event(CMD_EVENT_GAME_FOCUS_TOGGLE, &game_focus_cmd);
}
game_focus_st->enabled = false;
game_focus_st->core_requested = false;
}
#ifdef HAVE_OVERLAY
static bool video_driver_overlay_interface(
const video_overlay_interface_t **iface)
{
video_driver_state_t *video_st = video_state_get_ptr();
if (!video_st->current_video || !video_st->current_video->overlay_interface)
return false;
video_st->current_video->overlay_interface(video_st->data, iface);
return true;
}
static void input_overlay_enable_(bool enable)
{
settings_t *settings = config_get_ptr();
video_driver_state_t *video_st = video_state_get_ptr();
input_driver_state_t *input_st = &input_driver_st;
input_overlay_t *ol = input_st->overlay_ptr;
float opacity = (ol && (ol->flags & INPUT_OVERLAY_IS_OSK))
? settings->floats.input_osk_overlay_opacity
: settings->floats.input_overlay_opacity;
bool auto_rotate = settings->bools.input_overlay_auto_rotate;
bool hide_mouse_cursor = !settings->bools.input_overlay_show_mouse_cursor
&& settings->bools.video_fullscreen;
if (!ol)
return;
if (enable)
{
/* Set video interface */
ol->iface_data = video_st->data;
if (!video_driver_overlay_interface(&ol->iface) || !ol->iface)
{
RARCH_ERR("[Input] Overlay interface is not present in video driver.\n");
ol->flags &= ~INPUT_OVERLAY_ALIVE;
return;
}
/* Load last-active overlay */
input_overlay_load_active(input_st->overlay_visibility, ol, opacity);
/* Adjust to current settings */
command_event(CMD_EVENT_OVERLAY_SET_SCALE_FACTOR, NULL);
if (auto_rotate)
input_overlay_auto_rotate_(
video_st->width, video_st->height, true, ol);
/* Enable */
if (ol->iface->enable)
ol->iface->enable(ol->iface_data, true);
ol->flags |= (INPUT_OVERLAY_ENABLE | INPUT_OVERLAY_BLOCKED);
if ( hide_mouse_cursor
&& video_st->poke
&& video_st->poke->show_mouse)
video_st->poke->show_mouse(video_st->data, false);
}
else
{
/* Disable and clear input state */
ol->flags &= ~INPUT_OVERLAY_ENABLE;
input_st->flags &= ~INP_FLAG_BLOCK_POINTER_INPUT;
if (ol->iface && ol->iface->enable)
ol->iface->enable(ol->iface_data, false);
ol->iface = NULL;
memset(&ol->overlay_state, 0, sizeof(input_overlay_state_t));
memset(&ol->pointer_state, 0, sizeof(input_overlay_pointer_state_t));
}
}
static void input_overlay_deinit(void)
{
input_overlay_free(input_driver_st.overlay_ptr);
input_driver_st.overlay_ptr = NULL;
input_overlay_free(input_driver_st.overlay_cache_ptr);
input_driver_st.overlay_cache_ptr = NULL;
input_driver_st.flags &= ~INP_FLAG_BLOCK_POINTER_INPUT;
}
static void input_overlay_move_to_cache(void)
{
input_driver_state_t *input_st = &input_driver_st;
input_overlay_t *ol = input_st->overlay_ptr;
if (!ol)
return;
/* Free existing cache */
input_overlay_free(input_driver_st.overlay_cache_ptr);
/* Disable current overlay */
input_overlay_enable_(false);
/* Move to cache */
input_st->overlay_cache_ptr = ol;
input_st->overlay_ptr = NULL;
}
static void input_overlay_swap_with_cached(void)
{
input_driver_state_t *input_st = &input_driver_st;
input_overlay_t *ol;
/* Disable current overlay */
input_overlay_enable_(false);
/* Swap with cached */
ol = input_st->overlay_cache_ptr;
input_st->overlay_cache_ptr = input_st->overlay_ptr;
input_st->overlay_ptr = ol;
/* Enable and update to current settings */
input_overlay_enable_(true);
}
void input_overlay_unload(void)
{
bool input_overlay_enable = config_get_ptr()->bools.input_overlay_enable;
runloop_state_t *runloop_st = runloop_state_get_ptr();
/* Free if overlays disabled or initing/deiniting core */
if ( !input_overlay_enable
|| !(runloop_st->flags & RUNLOOP_FLAG_IS_INITED)
|| (runloop_st->flags & RUNLOOP_FLAG_SHUTDOWN_INITIATED))
input_overlay_deinit();
else
input_overlay_move_to_cache();
}
void input_overlay_set_visibility(int overlay_idx,
enum overlay_visibility vis)
{
input_driver_state_t *input_st = &input_driver_st;
input_overlay_t *ol = input_st->overlay_ptr;
if (!input_st->overlay_visibility)
{
unsigned i;
input_st->overlay_visibility = (enum overlay_visibility *)calloc(
MAX_VISIBILITY, sizeof(enum overlay_visibility));
/* NULL-check: the init loop below and the later
* overlay_visibility[overlay_idx] = vis write NULL-deref
* on OOM. Bail early - on failure the overlay stays at
* its compile-time default visibility rather than being
* explicitly set, which is strictly better than crashing. */
if (!input_st->overlay_visibility)
return;
for (i = 0; i < MAX_VISIBILITY; i++)
input_st->overlay_visibility[i] = OVERLAY_VISIBILITY_DEFAULT;
}
input_st->overlay_visibility[overlay_idx] = vis;
if (!ol)
return;
if (vis == OVERLAY_VISIBILITY_HIDDEN)
ol->iface->set_alpha(ol->iface_data, overlay_idx, 0.0);
}
static bool input_overlay_want_hidden(void)
{
settings_t *settings = config_get_ptr();
bool hide = false;
#ifdef HAVE_MENU
if (settings->bools.input_overlay_hide_in_menu)
hide = (menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE) != 0;
#endif
if (settings->bools.input_overlay_hide_when_gamepad_connected
&& !settings->bools.input_overlay_pointer_enable)
hide = hide || (input_config_get_device_name(0) != NULL);
return hide;
}
void input_overlay_check_mouse_cursor(void)
{
input_driver_state_t *input_st = &input_driver_st;
video_driver_state_t *video_st = video_state_get_ptr();
input_overlay_t *ol = input_st->overlay_ptr;
if ( ol && (ol->flags & INPUT_OVERLAY_ENABLE)
&& video_st->poke
&& video_st->poke->show_mouse)
{
if (config_get_ptr()->bools.input_overlay_show_mouse_cursor)
video_st->poke->show_mouse(video_st->data, true);
else if (input_st->flags & INP_FLAG_GRAB_MOUSE_STATE)
video_st->poke->show_mouse(video_st->data, false);
}
}
static void input_overlay_loaded_move_images(input_overlay_t *ol,
struct string_list *image_list)
{
size_t i;
ol->images = (struct texture_image**)malloc(ol->num_images * sizeof(struct texture_image *));
if (!ol->images)
{
for (i = 0; i < ol->num_images; i++)
image_texture_free((struct texture_image*)image_list->elems[i].attr.p);
ol->num_images = 0;
RARCH_ERR("[Overlay] Couldn't allocate images array.\n");
}
for (i = 0; i < ol->num_images; i++)
ol->images[i] = (struct texture_image*)image_list->elems[i].attr.p;
}
/* task_data = overlay_task_data_t* */
static void input_overlay_loaded(retro_task_t *task,
void *task_data, void *user_data, const char *err)
{
#ifdef HAVE_MENU
uint16_t overlay_types;
#endif
overlay_task_data_t *data = (overlay_task_data_t*)task_data;
input_overlay_t *ol = NULL;
input_driver_state_t *input_st = &input_driver_st;
bool enable_overlay = !input_overlay_want_hidden()
&& config_get_ptr()->bools.input_overlay_enable;
if (err)
return;
/* NULL-check task_data: the task producer (task_overlay_handler
* in tasks/task_overlay.c) can legitimately fail its data
* alloc under OOM and pass NULL here; data->overlays below
* would NULL-deref. Nothing useful to do with a NULL payload
* so bail cleanly. */
if (!data)
return;
ol = (input_overlay_t*)calloc(1, sizeof(*ol));
/* NULL-check the ol calloc: the field writes below NULL-deref
* on OOM. Task_data ownership transferred to us by the
* retro_task framework via task_set_data - we must tear it
* down cleanly. image_list entries each have a heap
* texture_image* in elems[i].attr.p that move_images would
* normally transfer into ol->images; since ol doesn't exist,
* free each texture explicitly before string_list_free
* (string_list_free only frees the list struct and its
* entries' .data strings, not their .attr.p attachments). */
if (!ol)
{
if (data->image_list)
{
size_t i;
for (i = 0; i < data->image_list->size; i++)
image_texture_free((struct texture_image*)data->image_list->elems[i].attr.p);
string_list_free(data->image_list);
}
free(data);
return;
}
ol->overlays = data->overlays;
ol->size = data->size;
ol->active = data->active;
ol->path = data->overlay_path;
ol->num_images = data->image_list->size;
ol->next_index = (unsigned)((ol->index + 1) % ol->size);
ol->flags |= INPUT_OVERLAY_ALIVE;
if (data->flags & OVERLAY_LOADER_IS_OSK)
ol->flags |= INPUT_OVERLAY_IS_OSK;
#ifdef HAVE_MENU
overlay_types = data->overlay_types;
#endif
if (ol->num_images > 0)
input_overlay_loaded_move_images(ol, data->image_list);
string_list_free(data->image_list);
free(data);
/* Due to the asynchronous nature of overlay loading
* it is possible for overlay_ptr to be non-NULL here
* > Ensure it is free()'d before assigning new pointer */
if (input_st->overlay_ptr)
input_overlay_free(input_st->overlay_ptr);
input_st->overlay_ptr = ol;
/* Enable or disable the overlay */
input_overlay_enable_(enable_overlay);
/* Abort if enable failed */
if (!(ol->flags & INPUT_OVERLAY_ALIVE))
{
input_st->overlay_ptr = NULL;
input_overlay_free(ol);
return;
}
/* Cache or free if hidden */
if (!enable_overlay)
input_overlay_unload();
/* Soft-hide when gamepad connected but pointer input enabled */
{
settings_t *settings = config_get_ptr();
if (enable_overlay
&& settings->bools.input_overlay_hide_when_gamepad_connected
&& settings->bools.input_overlay_pointer_enable
&& input_config_get_device_name(0) != NULL)
ol->flags |= INPUT_OVERLAY_GAMEPAD_HIDDEN;
}
input_overlay_set_eightway_diagonal_sensitivity();
/* Trigger viewport recalculation - overlay may have viewport override */
command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
#ifdef HAVE_MENU
/* Update menu entries if this is the main overlay */
if (!(ol->flags & INPUT_OVERLAY_IS_OSK))
{
struct menu_state *menu_st = menu_state_get_ptr();
if (menu_st->overlay_types != overlay_types)
{
menu_st->overlay_types = overlay_types;
menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH;
}
}
#endif
}
#define SYSTEM_OVERLAY_DIR "gamepads/Named_Overlays"
static const char *input_overlay_path(bool want_osk)
{
static char system_overlay_path[PATH_MAX_LENGTH] = {0};
char overlay_directory[PATH_MAX_LENGTH];
const char *a = NULL;
settings_t *settings = config_get_ptr();
playlist_t *playlist = playlist_get_cached();
core_info_t *core_info = NULL;
const char *content_path = path_get(RARCH_PATH_CONTENT);
if (want_osk)
return settings->paths.path_osk_overlay;
/* If the option is set to turn this off, just return default */
if (!settings->bools.input_overlay_enable_autopreferred)
return settings->paths.path_overlay;
/* If there's an override, use it */
if (retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_OVERLAY_PRESET, NULL))
return settings->paths.path_overlay;
/* If there's no core, just return the default */
a = path_get(RARCH_PATH_CORE);
if (!a || !*a)
return settings->paths.path_overlay;
/* Let's go hunting */
fill_pathname_expand_special(overlay_directory,
settings->paths.directory_overlay,
sizeof(overlay_directory));
/* Try based on the playlist entry first */
if (playlist)
{
if (content_path && *content_path)
{
const struct playlist_entry *entry = NULL;
playlist_get_index_by_path(playlist, content_path, &entry);
if (entry && entry->db_name)
{
size_t _len = fill_pathname_join_special_ext(system_overlay_path,
overlay_directory, SYSTEM_OVERLAY_DIR, entry->db_name, "",
sizeof(system_overlay_path));
char *ext = path_get_extension_mutable(system_overlay_path);
if (!ext)
ext = system_overlay_path + _len;
strlcpy(ext, ".cfg", 5);
if (path_is_valid(system_overlay_path))
return system_overlay_path;
}
}
}
/* Maybe the core info will have some clues... */
core_info_get_current_core(&core_info);
if (core_info)
{
if (core_info->databases_list && core_info->databases_list->size == 1)
{
fill_pathname_join_special_ext(system_overlay_path,
overlay_directory, SYSTEM_OVERLAY_DIR, core_info->databases_list->elems[0].data, ".cfg",
sizeof(system_overlay_path));
if (path_is_valid(system_overlay_path))
return system_overlay_path;
}
if (core_info->display_name)
{
fill_pathname_join_special_ext(system_overlay_path,
overlay_directory, SYSTEM_OVERLAY_DIR, core_info->display_name, ".cfg",
sizeof(system_overlay_path));
if (path_is_valid(system_overlay_path))
return system_overlay_path;
}
}
/* Maybe based on the content's directory name... */
if (content_path && *content_path)
{
char dirname[DIR_MAX_LENGTH];
fill_pathname_parent_dir_name(dirname, content_path, sizeof(dirname));
fill_pathname_join_special_ext(system_overlay_path,
overlay_directory, SYSTEM_OVERLAY_DIR, dirname, ".cfg",
sizeof(system_overlay_path));
if (path_is_valid(system_overlay_path))
return system_overlay_path;
}
/* I give up */
return settings->paths.path_overlay;
}
void input_overlay_init(void)
{
settings_t *settings = config_get_ptr();
input_driver_state_t *input_st = &input_driver_st;
input_overlay_t *ol = input_st->overlay_ptr;
input_overlay_t *ol_cache = input_st->overlay_cache_ptr;
bool want_osk =
(input_st->flags & INP_FLAG_KB_LINEFEED_ENABLE)
&& *settings->paths.path_osk_overlay;
const char *path_overlay = input_overlay_path(want_osk);
bool want_hidden = input_overlay_want_hidden();
bool overlay_shown = ol
&& (ol->flags & INPUT_OVERLAY_ENABLE)
&& string_is_equal(path_overlay, ol->path);
bool overlay_cached = ol_cache
&& (ol_cache->flags & INPUT_OVERLAY_ALIVE)
&& string_is_equal(path_overlay, ol_cache->path);
bool overlay_hidden = !ol && overlay_cached;
#if defined(GEKKO)
/* Avoid a crash at startup or even when toggling overlay in rgui */
if (frontend_driver_get_free_memory() < (3 * 1024 * 1024))
return;
#endif
/* Cancel if overlays disabled or task already done */
if ( !settings->bools.input_overlay_enable
|| ( want_hidden && overlay_hidden)
|| (!want_hidden && overlay_shown))
return;
/* Restore if cached */
if (!want_hidden && overlay_cached)
{
input_overlay_swap_with_cached();
return;
}
/* Cache current overlay when loading a different type */
if (want_osk != (ol && (ol->flags & INPUT_OVERLAY_IS_OSK)))
input_overlay_unload();
else
input_overlay_deinit();
/* Start task */
task_push_overlay_load_default(
input_overlay_loaded, path_overlay, want_osk, NULL);
}
#endif
void input_pad_connect(unsigned port, input_device_driver_t *driver)
{
if (port >= MAX_USERS || !driver)
{
RARCH_ERR("[Input] input_pad_connect: Bad parameters.\n");
return;
}
input_autoconfigure_connect(driver->name(port), NULL, NULL, driver->ident,
port, 0, 0);
}
static bool input_keys_pressed_other_sources(
input_driver_state_t *input_st,
unsigned i,
input_bits_t *p_new_state)
{
#ifdef HAVE_COMMAND
int j;
for (j = 0; j < (int)ARRAY_SIZE(input_st->command); j++)
if ((i < RARCH_BIND_LIST_END) && input_st->command[j]
&& input_st->command[j]->state[i])
return true;
#endif
#ifdef HAVE_OVERLAY
if ( input_st->overlay_ptr &&
((BIT256_GET(input_st->overlay_ptr->overlay_state.buttons, i))))
return true;
#endif
#ifdef HAVE_NETWORKGAMEPAD
/* Only process key presses related to game input if using Remote RetroPad */
if (i < RARCH_CUSTOM_BIND_LIST_END
&& input_st->remote
&& INPUT_REMOTE_KEY_PRESSED(input_st, i, 0))
return true;
#endif
return false;
}
#define CHECK_GAME_FOCUS_ENABLE_HOTKEY_COMBO(i) \
if ( (input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED) \
&& ( (i == RARCH_ENABLE_HOTKEY) \
|| (i != RARCH_ENABLE_HOTKEY && !block_hotkey[RARCH_ENABLE_HOTKEY])) \
) \
{ \
if (input_state_wrap( \
input_st->current_driver, \
input_st->current_data, \
input_st->primary_joypad, \
sec_joypad, \
joypad_info, \
binds, \
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED), \
port, RETRO_DEVICE_JOYPAD, 0, \
i)) \
block_hotkey[i] = false; \
} \
/**
* input_keys_pressed:
*
* Grab an input sample for this frame.
*/
static void input_keys_pressed(
unsigned port,
unsigned hotkey_port,
bool is_menu,
unsigned input_hotkey_block_delay,
input_bits_t *p_new_state,
const retro_keybind_set *binds,
const struct retro_keybind *binds_norm,
const struct retro_keybind *binds_auto,
const input_device_driver_t *joypad,
const input_device_driver_t *sec_joypad,
rarch_joypad_info_t *joypad_info,
bool input_hotkey_device_merge)
{
unsigned i;
/* Autoconf binds are indexed by joy_idx, not frontend port */
unsigned joy_idx = joypad_info->joy_idx;
int32_t ret = 0;
input_driver_state_t *input_st = &input_driver_st;
bool block_hotkey[RARCH_BIND_LIST_END];
bool enable_hotkey_pressed = false;
bool any_pressed = false;
bool libretro_hotkey_set =
binds_norm->joykey != NO_BTN
|| binds_norm->joyaxis != AXIS_NONE
|| binds_auto->joykey != NO_BTN
|| binds_auto->joyaxis != AXIS_NONE;
bool keyboard_hotkey_set =
binds_norm->key != RETROK_UNKNOWN;
if (!binds)
return;
if ( input_hotkey_device_merge
&& (libretro_hotkey_set || keyboard_hotkey_set))
libretro_hotkey_set = keyboard_hotkey_set = true;
/* Cache once - flag does not change during a single poll */
{
bool kb_blocked = !!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED);
if ( (port == hotkey_port)
&& (binds_norm->valid || binds_auto->valid)
&& CHECK_INPUT_DRIVER_BLOCK_HOTKEY(binds_norm, binds_auto))
{
if (input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
sec_joypad,
joypad_info,
binds,
kb_blocked,
port, RETRO_DEVICE_JOYPAD, 0,
RARCH_ENABLE_HOTKEY))
{
enable_hotkey_pressed = true;
if (input_st->input_hotkey_block_counter < input_hotkey_block_delay)
input_st->input_hotkey_block_counter++;
else
input_st->flags |= INP_FLAG_BLOCK_LIBRETRO_INPUT;
}
else
input_st->flags |= INP_FLAG_BLOCK_HOTKEY;
}
#ifdef HAVE_MENU
/* Prevent triggering menu actions after binding */
if ( !(input_st->flags & INP_FLAG_MENU_PRESS_PENDING)
&& menu_state_get_ptr()->input_driver_flushing_input)
input_st->flags |= INP_FLAG_WAIT_INPUT_RELEASE;
#endif
/* Check libretro input if emulated device type is active,
* except device type must be always active in menu. */
if ( !(input_st->flags & INP_FLAG_BLOCK_LIBRETRO_INPUT)
&& !(!is_menu && !input_config_get_device(port)))
ret = input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
sec_joypad,
joypad_info,
binds,
kb_blocked,
port, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_MASK);
for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
{
if ( (ret & (UINT64_C(1) << i))
|| input_keys_pressed_other_sources(input_st, i, p_new_state))
{
any_pressed = true;
if (input_st->flags & INP_FLAG_WAIT_INPUT_RELEASE)
continue;
BIT256_SET_PTR(p_new_state, i);
}
}
/* Allow menu toggle to bypass 'enable_hotkey' when it is
* not part of the usual buttons, unless 'enable_hotkey'
* is set in autoconf. */
if ( !any_pressed
&& !(input_st->flags & INP_FLAG_WAIT_INPUT_RELEASE)
&& (binds[port][RARCH_MENU_TOGGLE].joykey != NO_BTN)
&& ( input_autoconf_binds[joy_idx][RARCH_ENABLE_HOTKEY].joykey == binds[port][RARCH_MENU_TOGGLE].joykey
|| input_autoconf_binds[joy_idx][RARCH_ENABLE_HOTKEY].joykey == NO_BTN))
{
/* Ignore keyboard menu toggle button and check
* joypad menu toggle button for pressing
* it without 'enable_hotkey', because Guide button
* is not part of the usual buttons. */
i = RARCH_MENU_TOGGLE;
if (!(binds[port][i].valid
&& input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
sec_joypad,
joypad_info,
binds,
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
port, RETRO_DEVICE_KEYBOARD, 0,
input_config_binds[port][i].key)))
{
bool bit_pressed = binds[port][i].valid
&& input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
sec_joypad,
joypad_info,
binds,
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
port, RETRO_DEVICE_JOYPAD, 0, i);
if ( bit_pressed
|| BIT64_GET(lifecycle_state, i)
|| input_keys_pressed_other_sources(input_st, i, p_new_state))
{
if (!(input_st->flags & INP_FLAG_MENU_PRESS_PENDING))
input_st->flags &= ~INP_FLAG_MENU_PRESS_CANCEL;
input_st->flags |= INP_FLAG_MENU_PRESS_PENDING;
}
else if (input_st->flags & INP_FLAG_MENU_PRESS_PENDING)
/* Also set 'enable_hotkey' to prevent hotkey delay untrigger */
BIT256_SET_PTR(p_new_state, RARCH_ENABLE_HOTKEY);
}
}
/* Ignore hotkey block delay when menu toggle and hotkey enabler share the same key */
if ( !any_pressed
&& !(input_st->flags & INP_FLAG_WAIT_INPUT_RELEASE)
&& !(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED)
&& binds[port][RARCH_MENU_TOGGLE].key == binds[port][RARCH_ENABLE_HOTKEY].key)
{
i = RARCH_MENU_TOGGLE;
if ( binds[port][i].valid
&& input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
sec_joypad,
joypad_info,
binds,
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
port, RETRO_DEVICE_KEYBOARD, 0,
input_config_binds[port][i].key))
input_st->flags |= INP_FLAG_MENU_PRESS_PENDING;
else if (input_st->flags & INP_FLAG_MENU_PRESS_PENDING)
/* Also set 'enable_hotkey' to prevent hotkey delay untrigger */
BIT256_SET_PTR(p_new_state, RARCH_ENABLE_HOTKEY);
}
/* Hotkeys are only relevant for the first user or core port */
if (port != hotkey_port)
return;
/* Check hotkeys to block keyboard and joypad hotkeys separately.
* This looks complicated because hotkeys must be unblocked based
* on the device type depending if 'enable_hotkey' is set or not.. */
if ( input_st->flags & INP_FLAG_BLOCK_HOTKEY
&& (libretro_hotkey_set && keyboard_hotkey_set))
{
/* Block everything when hotkey bind exists for both device types */
for (i = RARCH_FIRST_META_KEY; i < RARCH_BIND_LIST_END; i++)
{
block_hotkey[i] = true;
/* Don't block controller hotkey enabler with Game Focus */
CHECK_GAME_FOCUS_ENABLE_HOTKEY_COMBO(i);
}
}
else if (input_st->flags & INP_FLAG_BLOCK_HOTKEY
&& (!libretro_hotkey_set || !keyboard_hotkey_set))
{
/* Block selectively when hotkey bind exists for either device type */
for (i = RARCH_FIRST_META_KEY; i < RARCH_BIND_LIST_END; i++)
{
bool keyboard_hotkey_pressed = false;
bool libretro_hotkey_pressed = false;
/* Default */
block_hotkey[i] = true;
/* No 'enable_hotkey' in joypad */
if (!libretro_hotkey_set)
{
if ( binds[port][i].joykey != NO_BTN
|| binds[port][i].joyaxis != AXIS_NONE)
{
/* Allow blocking if keyboard hotkey is pressed */
if (input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
sec_joypad,
joypad_info,
binds,
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
port, RETRO_DEVICE_KEYBOARD, 0,
input_config_binds[port][i].key))
{
keyboard_hotkey_pressed = true;
/* Always block */
block_hotkey[i] = true;
}
/* Deny blocking if joypad hotkey is pressed */
if (input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
sec_joypad,
joypad_info,
binds,
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
port, RETRO_DEVICE_JOYPAD, 0,
i))
{
libretro_hotkey_pressed = true;
/* Only deny block if keyboard is not pressed */
if (!keyboard_hotkey_pressed)
block_hotkey[i] = false;
}
}
}
/* No 'enable_hotkey' in keyboard */
if (!keyboard_hotkey_set)
{
if (binds[port][i].key != RETROK_UNKNOWN)
{
/* Deny blocking if keyboard hotkey is pressed */
if (input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
sec_joypad,
joypad_info,
binds,
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
port, RETRO_DEVICE_KEYBOARD, 0,
input_config_binds[port][i].key))
{
keyboard_hotkey_pressed = true;
/* Only deny block if joypad is not pressed */
if (!libretro_hotkey_pressed)
block_hotkey[i] = false;
}
/* Allow blocking if joypad hotkey is pressed */
if (input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
sec_joypad,
joypad_info,
binds,
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
port, RETRO_DEVICE_JOYPAD, 0,
i))
{
/* Only block if keyboard is not pressed */
if (!keyboard_hotkey_pressed)
block_hotkey[i] = true;
}
}
}
/* Don't block controller hotkey enabler with Game Focus */
CHECK_GAME_FOCUS_ENABLE_HOTKEY_COMBO(i);
}
}
else
{
/* Clear everything */
for (i = RARCH_FIRST_META_KEY; i < RARCH_BIND_LIST_END; i++)
block_hotkey[i] = false;
}
if (!is_menu && binds[port][RARCH_GAME_FOCUS_TOGGLE].valid)
{
/* Never block Game Focus toggle hotkey */
block_hotkey[RARCH_GAME_FOCUS_TOGGLE] = false;
}
for (i = RARCH_FIRST_META_KEY; i < RARCH_BIND_LIST_END; i++)
{
bool other_pressed = input_keys_pressed_other_sources(input_st, i, p_new_state);
bool bit_pressed = binds[port][i].valid
&& input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
sec_joypad,
joypad_info,
binds,
kb_blocked,
port, RETRO_DEVICE_JOYPAD, 0,
i);
if ( bit_pressed
|| other_pressed
|| BIT64_GET(lifecycle_state, i))
{
any_pressed = true;
if (input_st->flags & INP_FLAG_WAIT_INPUT_RELEASE)
continue;
if (libretro_hotkey_set || keyboard_hotkey_set)
{
/* Do not block "other source" (input overlay) presses */
if (block_hotkey[i] && !other_pressed)
continue;
}
/* Set menu toggle on release */
if (i == RARCH_MENU_TOGGLE)
{
if (!(input_st->flags & INP_FLAG_MENU_PRESS_PENDING))
{
input_st->flags |= INP_FLAG_MENU_PRESS_PENDING;
input_st->flags &= ~INP_FLAG_MENU_PRESS_CANCEL;
}
continue;
}
else if (i != RARCH_ENABLE_HOTKEY)
{
input_st->flags |= INP_FLAG_MENU_PRESS_CANCEL;
/* Game Focus toggle is always allowed, so it must clear menu cancel */
if (i == RARCH_GAME_FOCUS_TOGGLE)
input_st->flags &= ~INP_FLAG_MENU_PRESS_CANCEL;
}
BIT256_SET_PTR(p_new_state, i);
}
else
{
if (i == RARCH_MENU_TOGGLE)
{
/* Untrigger menu if press was shorter than hotkey block delay */
if ( (input_st->flags & INP_FLAG_MENU_PRESS_PENDING)
&& !(input_st->flags & INP_FLAG_MENU_PRESS_CANCEL)
&& !BIT256_GET_PTR(p_new_state, RARCH_ENABLE_HOTKEY)
&& input_st->input_hotkey_block_counter
&& input_st->input_hotkey_block_counter < input_hotkey_block_delay)
input_st->flags &= ~INP_FLAG_MENU_PRESS_PENDING;
if (input_st->flags & INP_FLAG_MENU_PRESS_PENDING)
{
/* Forget menu press if any other hotkey was pressed */
if (!(input_st->flags & INP_FLAG_MENU_PRESS_CANCEL))
BIT256_SET_PTR(p_new_state, i);
input_st->flags &= ~(INP_FLAG_MENU_PRESS_PENDING | INP_FLAG_MENU_PRESS_CANCEL);
}
}
}
}
if ((input_st->flags & INP_FLAG_WAIT_INPUT_RELEASE) && !any_pressed)
input_st->flags &= ~INP_FLAG_WAIT_INPUT_RELEASE;
if (input_st->flags & INP_FLAG_BLOCK_HOTKEY && !enable_hotkey_pressed)
input_st->input_hotkey_block_counter = 0;
} /* kb_blocked scope */
}
void input_driver_poll(void)
{
size_t i, j;
rarch_joypad_info_t joypad_info[MAX_USERS];
input_driver_state_t *input_st = &input_driver_st;
const input_driver_t *input = (const input_driver_t*)
input_st->current_driver;
settings_t *settings = config_get_ptr();
const input_device_driver_t
*joypad = input_st->primary_joypad;
#ifdef HAVE_MFI
const input_device_driver_t
*sec_joypad = input_st->secondary_joypad;
#else
const input_device_driver_t
*sec_joypad = NULL;
#endif
bool input_remap_binds_enable = settings->bools.input_remap_binds_enable;
float input_axis_threshold = settings->floats.input_axis_threshold;
uint8_t max_users = (uint8_t)settings->uints.input_max_users;
if (joypad && joypad->poll)
joypad->poll();
if (sec_joypad && sec_joypad->poll)
sec_joypad->poll();
if (input && input->poll)
input->poll(input_st->current_data);
/* Invalidate joypad state bitmask cache for the new frame */
memset(input_st->joypad_state_cache_valid, 0,
sizeof(input_st->joypad_state_cache_valid));
/* Enable/disable sensors at the driver level based on demand
* from shaders and/or core. Setting gates everything. */
if (settings->bools.input_sensors_enable)
{
bool want = input_st->shader_uses_sensors
|| input_st->core_accel_rate
|| input_st->core_gyro_rate;
if (want && !input_st->frontend_sensors_enabled)
{
input_set_sensor_state(0, RETRO_SENSOR_ACCELEROMETER_ENABLE,
input_st->core_accel_rate ? input_st->core_accel_rate : 60);
input_set_sensor_state(0, RETRO_SENSOR_GYROSCOPE_ENABLE,
input_st->core_gyro_rate ? input_st->core_gyro_rate : 60);
input_st->frontend_sensors_enabled = true;
input_sensor_start_rest_capture();
}
else if (!want && input_st->frontend_sensors_enabled)
{
input_set_sensor_state(0, RETRO_SENSOR_ACCELEROMETER_DISABLE, 0);
input_set_sensor_state(0, RETRO_SENSOR_GYROSCOPE_DISABLE, 0);
input_st->frontend_sensors_enabled = false;
}
}
else if (input_st->frontend_sensors_enabled)
{
input_set_sensor_state(0, RETRO_SENSOR_ACCELEROMETER_DISABLE, 0);
input_set_sensor_state(0, RETRO_SENSOR_GYROSCOPE_DISABLE, 0);
input_st->frontend_sensors_enabled = false;
}
/* Update accelerometer rest position capture (runs for ~30 frames
* after focus gain, then stops) */
input_sensor_update_rest_capture(settings);
/* Cache sensor values so shader backends on the video thread
* read a consistent per-frame snapshot instead of calling into
* the input subsystem directly.
* Uses the internal variant to avoid 6 redundant config_get_ptr() calls.
*
* When the master input_sensors_enable toggle is off (the common
* default), input_get_sensor_state_internal would return 0.0 for
* every call after two settings reads and a couple of branches per
* call. Short-circuit that to a single memset since the result is
* the same. */
if (!settings->bools.input_sensors_enable)
{
memset(input_st->sensor_gyroscope_cache, 0,
sizeof(input_st->sensor_gyroscope_cache));
memset(input_st->sensor_accelerometer_cache, 0,
sizeof(input_st->sensor_accelerometer_cache));
}
else
{
input_st->sensor_gyroscope_cache[0] = input_get_sensor_state_internal(settings, 0, RETRO_SENSOR_GYROSCOPE_X);
input_st->sensor_gyroscope_cache[1] = input_get_sensor_state_internal(settings, 0, RETRO_SENSOR_GYROSCOPE_Y);
input_st->sensor_gyroscope_cache[2] = input_get_sensor_state_internal(settings, 0, RETRO_SENSOR_GYROSCOPE_Z);
input_st->sensor_accelerometer_cache[0] = input_get_sensor_state_internal(settings, 0, RETRO_SENSOR_ACCELEROMETER_X);
input_st->sensor_accelerometer_cache[1] = input_get_sensor_state_internal(settings, 0, RETRO_SENSOR_ACCELEROMETER_Y);
input_st->sensor_accelerometer_cache[2] = input_get_sensor_state_internal(settings, 0, RETRO_SENSOR_ACCELEROMETER_Z);
}
#ifdef HAVE_OVERLAY
if ( input_st->overlay_ptr
&& (input_st->overlay_ptr->flags & INPUT_OVERLAY_ALIVE))
{
unsigned input_analog_dpad_mode = settings->uints.input_analog_dpad_mode[0];
float input_overlay_opacity = (input_st->overlay_ptr->flags & INPUT_OVERLAY_IS_OSK)
? settings->floats.input_osk_overlay_opacity
: settings->floats.input_overlay_opacity;
switch (input_analog_dpad_mode)
{
case ANALOG_DPAD_LSTICK:
case ANALOG_DPAD_RSTICK:
case ANALOG_DPAD_LRSTICK:
case ANALOG_DPAD_TWINSTICK:
{
unsigned mapped_port = settings->uints.input_remap_ports[0];
if (input_st->analog_requested[mapped_port])
input_analog_dpad_mode = ANALOG_DPAD_NONE;
}
break;
case ANALOG_DPAD_LSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_LSTICK;
break;
case ANALOG_DPAD_RSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_RSTICK;
break;
case ANALOG_DPAD_LRSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_LRSTICK;
break;
case ANALOG_DPAD_TWINSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_TWINSTICK;
break;
default:
break;
}
input_poll_overlay(
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
settings,
input_st->overlay_ptr,
input_st->overlay_visibility,
input_overlay_opacity,
input_analog_dpad_mode,
settings->floats.input_axis_threshold);
}
#endif
input_st->turbo_btns.count++;
if (input_st->flags & INP_FLAG_BLOCK_LIBRETRO_INPUT)
{
for (i = 0; i < max_users; i++)
{
input_st->turbo_btns.frame_enable[i] = 0;
input_st->hold_btns.frame_enable[i] = 0;
}
return;
}
/* Fused turbo/hold + remap loop.
*
* Previously these were two separate max_users iterations:
* 1) turbo/hold: init joypad_info[i], call input_state_wrap ×2
* 2) remap: reuse joypad_info[i], do remap work
* Now fused into a single pass to improve cache locality —
* joypad_info[i], bind arrays, and joypad driver state stay hot
* in L1 across all per-user work, and the loop overhead is halved. */
{
bool turbo_enable = settings->bools.input_turbo_enable;
uint16_t turbo_btn_id = RARCH_TURBO_ENABLE;
bool kb_blocked = !!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED);
#ifdef HAVE_MENU
bool do_remap = input_remap_binds_enable
&& !(menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE);
#else
bool do_remap = input_remap_binds_enable;
#endif
#ifdef HAVE_OVERLAY
input_overlay_t *overlay_pointer = (input_overlay_t*)input_st->overlay_ptr;
bool poll_overlay = (overlay_pointer &&
(overlay_pointer->flags & INPUT_OVERLAY_ALIVE));
#endif
input_mapper_t *handle = &input_st->mapper;
float input_analog_deadzone = settings->floats.input_analog_deadzone;
float input_analog_sensitivity = settings->floats.input_analog_sensitivity;
if (settings->ints.input_turbo_bind != -1)
turbo_btn_id = settings->ints.input_turbo_bind;
for (i = 0; i < max_users; i++)
{
/* --- joypad_info init (shared by turbo/hold and remap) --- */
joypad_info[i].axis_threshold = input_axis_threshold;
joypad_info[i].joy_idx = settings->uints.input_joypad_index[i];
joypad_info[i].auto_binds = input_autoconf_binds[joypad_info[i].joy_idx];
/* --- Turbo button state --- */
input_st->turbo_btns.frame_enable[i] =
(*input_st->libretro_input_binds[i])[turbo_btn_id].valid
&& turbo_enable ?
input_state_wrap(input_st->current_driver,
input_st->current_data,
joypad, sec_joypad, &joypad_info[i],
(*input_st->libretro_input_binds),
kb_blocked,
(unsigned)i,
RETRO_DEVICE_JOYPAD, 0, turbo_btn_id) : 0;
#ifdef HAVE_OVERLAY
if ( (i == 0)
&& turbo_enable
&& overlay_pointer
&& (overlay_pointer->flags & INPUT_OVERLAY_ALIVE)
&& BIT256_GET(overlay_pointer->overlay_state.buttons, turbo_btn_id))
input_st->turbo_btns.frame_enable[i] = true;
#endif
/* --- Hold button modifier state --- */
input_st->hold_btns.frame_enable[i] =
(*input_st->libretro_input_binds[i])[RARCH_HOLD_ENABLE].valid ?
input_state_wrap(input_st->current_driver,
input_st->current_data,
joypad, sec_joypad, &joypad_info[i],
(*input_st->libretro_input_binds),
kb_blocked,
(unsigned)i,
RETRO_DEVICE_JOYPAD, 0, RARCH_HOLD_ENABLE) : 0;
#ifdef HAVE_OVERLAY
if ( (i == 0)
&& overlay_pointer
&& (overlay_pointer->flags & INPUT_OVERLAY_ALIVE)
&& BIT256_GET(overlay_pointer->overlay_state.buttons, RARCH_HOLD_ENABLE))
input_st->hold_btns.frame_enable[i] = true;
#endif
/* --- Remap work (conditional) --- */
if (do_remap)
{
input_bits_t current_inputs;
unsigned mapped_port = settings->uints.input_remap_ports[i];
unsigned device = settings->uints.input_libretro_device[mapped_port]
& RETRO_DEVICE_MASK;
input_bits_t *p_new_state = (input_bits_t*)&current_inputs;
unsigned input_analog_dpad_mode = settings->uints.input_analog_dpad_mode[i];
switch (input_analog_dpad_mode)
{
case ANALOG_DPAD_LSTICK:
case ANALOG_DPAD_RSTICK:
case ANALOG_DPAD_LRSTICK:
case ANALOG_DPAD_TWINSTICK:
if (input_st->analog_requested[mapped_port])
input_analog_dpad_mode = ANALOG_DPAD_NONE;
break;
case ANALOG_DPAD_LSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_LSTICK;
break;
case ANALOG_DPAD_RSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_RSTICK;
break;
case ANALOG_DPAD_LRSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_LRSTICK;
break;
case ANALOG_DPAD_TWINSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_TWINSTICK;
break;
default:
break;
}
switch (device)
{
case RETRO_DEVICE_KEYBOARD:
case RETRO_DEVICE_JOYPAD:
case RETRO_DEVICE_ANALOG:
BIT256_CLEAR_ALL_PTR(&current_inputs);
if (joypad)
{
unsigned k;
int32_t ret = input_state_wrap(
input_st->current_driver,
input_st->current_data,
input_st->primary_joypad,
sec_joypad,
&joypad_info[i],
(*input_st->libretro_input_binds),
kb_blocked,
(unsigned)i, RETRO_DEVICE_JOYPAD,
0, RETRO_DEVICE_ID_JOYPAD_MASK);
for (k = 0; k < RARCH_FIRST_CUSTOM_BIND; k++)
{
if (ret & (1 << k))
{
bool valid_bind =
(*input_st->libretro_input_binds[i])[k].valid;
if (valid_bind)
{
int16_t val =
input_joypad_analog_button(
input_analog_deadzone,
input_analog_sensitivity,
joypad,
&joypad_info[i],
k,
&(*input_st->libretro_input_binds[i])[k]
);
if (val)
p_new_state->analog_buttons[k] = val;
}
BIT256_SET_PTR(p_new_state, k);
}
}
/* Process both axes of each analog stick together,
* computing radial deadzone magnitude only once
* per stick instead of once per axis. */
for (k = 0; k < 2; k++)
{
int16_t stick_x = 0;
int16_t stick_y = 0;
if (input_joypad_analog_stick(
input_analog_dpad_mode,
input_analog_deadzone,
input_analog_sensitivity,
joypad, &joypad_info[i],
k, (*input_st->libretro_input_binds[i]),
&stick_x, &stick_y))
{
unsigned off_x = 0 + (k * 4);
unsigned off_y = 0 + (k * 4) + 2;
if (stick_x >= 0)
p_new_state->analogs[off_x] = stick_x;
else
p_new_state->analogs[off_x + 1] = stick_x;
if (stick_y >= 0)
p_new_state->analogs[off_y] = stick_y;
else
p_new_state->analogs[off_y + 1] = stick_y;
}
}
}
break;
default:
break;
}
/* mapper */
switch (device)
{
/* keyboard to gamepad remapping */
case RETRO_DEVICE_KEYBOARD:
for (j = 0; j < RARCH_CUSTOM_BIND_LIST_END; j++)
{
unsigned current_button_value;
unsigned remap_key =
settings->uints.input_keymapper_ids[i][j];
if (remap_key == RETROK_UNKNOWN)
continue;
if (j >= RARCH_FIRST_CUSTOM_BIND && j < RARCH_ANALOG_BIND_LIST_END)
{
int16_t current_axis_value = p_new_state->analogs[j - RARCH_FIRST_CUSTOM_BIND];
current_button_value = abs(current_axis_value) >
settings->floats.input_axis_threshold
* 32767;
}
else
current_button_value = BIT256_GET_PTR(p_new_state, j);
#ifdef HAVE_OVERLAY
if (poll_overlay && i == 0)
{
input_overlay_state_t *ol_state = overlay_pointer
? &overlay_pointer->overlay_state : NULL;
if (ol_state)
current_button_value |= BIT256_GET(ol_state->buttons, j);
}
#endif
/* Press */
if ((current_button_value == 1)
&& !MAPPER_GET_KEY(handle, remap_key))
{
handle->key_button[remap_key] = (unsigned)j;
MAPPER_SET_KEY(handle, remap_key);
input_keyboard_event(true,
remap_key,
0, 0, RETRO_DEVICE_KEYBOARD);
}
/* Release */
else if ((current_button_value == 0)
&& MAPPER_GET_KEY(handle, remap_key))
{
if (handle->key_button[remap_key] != j)
continue;
input_keyboard_event(false,
remap_key,
0, 0, RETRO_DEVICE_KEYBOARD);
MAPPER_UNSET_KEY(handle, remap_key);
}
}
break;
/* gamepad remapping */
case RETRO_DEVICE_JOYPAD:
case RETRO_DEVICE_ANALOG:
/* this loop iterates on all users and all buttons,
* and checks if a pressed button is assigned to any
* other button than the default one, then it sets
* the bit on the mapper input bitmap, later on the
* original input is cleared in input_state */
BIT256_CLEAR_ALL(handle->buttons[i]);
for (j = 0; j < 8; j++)
handle->analog_value[i][j] = 0;
for (j = 0; j < RARCH_FIRST_CUSTOM_BIND; j++)
{
bool remap_valid;
unsigned remap_button =
settings->uints.input_remap_ids[i][j];
unsigned current_button_value =
BIT256_GET_PTR(p_new_state, j);
#ifdef HAVE_OVERLAY
if (poll_overlay && i == 0)
{
input_overlay_state_t *ol_state =
overlay_pointer
? &overlay_pointer->overlay_state
: NULL;
if (ol_state)
current_button_value |= BIT256_GET(ol_state->buttons, j);
}
#endif
remap_valid =
(current_button_value == 1)
&& (j != remap_button)
&& (remap_button != RARCH_UNMAPPED);
#ifdef HAVE_ACCESSIBILITY
/* gamepad override */
if ( (i == 0)
&& input_st->gamepad_input_override & (1 << j))
{
BIT256_SET(handle->buttons[i], j);
}
#endif
if (remap_valid)
{
if (remap_button < RARCH_FIRST_CUSTOM_BIND)
{
BIT256_SET(handle->buttons[i], remap_button);
}
else
{
unsigned remap_axis_bind =
remap_button - RARCH_FIRST_CUSTOM_BIND;
int invert = 1;
/* Bound: analog_value is int16_t[8]. remap_button
* comes from settings->uints.input_remap_ids[][]
* which is loaded from .rmp config files;
* input_remapping_load_file's config_get_int
* accepts any integer, so a malformed .rmp can
* supply remap_button anywhere in (RARCH_FIRST_
* CUSTOM_BIND, INT_MAX], producing a write at
* arbitrary offset past analog_value[i]. Reject
* out-of-range targets here. */
if (remap_axis_bind >= ARRAY_SIZE(handle->analog_value[i]))
continue;
if (remap_button % 2 != 0)
invert = -1;
handle->analog_value[i][remap_axis_bind] =
(p_new_state->analog_buttons[j]
? p_new_state->analog_buttons[j]
: 32767) * invert;
}
}
}
for (j = 0; j < 8; j++)
{
unsigned k = (unsigned)j + RARCH_FIRST_CUSTOM_BIND;
int16_t current_axis_value = p_new_state->analogs[j];
unsigned remap_axis = settings->uints.input_remap_ids[i][k];
if (
(
abs(current_axis_value) > 0
&& (k != remap_axis)
&& (remap_axis != RARCH_UNMAPPED)
)
)
{
if (remap_axis < RARCH_FIRST_CUSTOM_BIND &&
abs(current_axis_value) >
settings->floats.input_axis_threshold
* 32767)
{
BIT256_SET(handle->buttons[i], remap_axis);
}
else
{
unsigned remap_axis_bind =
remap_axis - RARCH_FIRST_CUSTOM_BIND;
/* Pre-patch this read 'sizeof(handle->analog_value[i])'
* which is 16 (bytes), but the array has 8 elements;
* any remap_axis_bind in [8, 15] caused an OOB write.
* Use ARRAY_SIZE so the check is in elements. */
if (remap_axis_bind < ARRAY_SIZE(handle->analog_value[i]))
{
int invert = 1;
if ( (k % 2 == 0 && remap_axis % 2 != 0)
|| (k % 2 != 0 && remap_axis % 2 == 0)
)
invert = -1;
handle->analog_value[i][
remap_axis_bind] =
current_axis_value * invert;
}
}
}
}
break;
default:
break;
}
} /* if (do_remap) */
} /* for (i = 0; i < max_users; i++) */
}
#ifdef HAVE_COMMAND
for (i = 0; i < ARRAY_SIZE(input_st->command); i++)
{
if (input_st->command[i])
{
memset(input_st->command[i]->state,
0, sizeof(input_st->command[i]->state));
input_st->command[i]->poll(
input_st->command[i]);
}
}
#endif
#ifdef HAVE_NETWORKGAMEPAD
/* Poll remote */
if (input_st->remote)
{
unsigned user;
for (user = 0; user < max_users; user++)
{
if (settings->bools.network_remote_enable_user[user])
{
#if defined(HAVE_NETWORKING) && defined(HAVE_NETWORKGAMEPAD)
fd_set fds;
ssize_t ret;
struct remote_message msg;
#if defined(_WIN32)
if (input_st->remote->net_fd[user] == INVALID_SOCKET)
#else
if (input_st->remote->net_fd[user] < 0)
#endif
return;
FD_ZERO(&fds);
FD_SET(input_st->remote->net_fd[user], &fds);
ret = recvfrom(input_st->remote->net_fd[user],
(char*)&msg,
sizeof(msg), 0, NULL, NULL);
if (ret == sizeof(msg))
input_remote_parse_packet(&input_st->remote_st_ptr, &msg, user);
else if ((ret != -1) || ((errno != EAGAIN) && (errno != ENOENT)))
#endif
{
input_remote_state_t *input_state = &input_st->remote_st_ptr;
input_state->buttons[user] = 0;
input_state->analog[0][user] = 0;
input_state->analog[1][user] = 0;
input_state->analog[2][user] = 0;
input_state->analog[3][user] = 0;
}
}
}
}
#endif
#ifdef HAVE_BSV_MOVIE
if (BSV_MOVIE_IS_PLAYBACK_ON())
bsv_movie_poll(input_st);
#endif
}
int16_t input_driver_state_wrapper(unsigned port, unsigned device,
unsigned idx, unsigned id)
{
input_driver_state_t
*input_st = &input_driver_st;
settings_t *settings = config_get_ptr();
int16_t result = 0;
#ifdef HAVE_BSV_MOVIE
if (BSV_MOVIE_IS_PLAYBACK_ON())
return bsv_movie_read_state(input_st, port, device, idx, id);
#endif
/* Read input state */
result = input_state_internal(input_st, settings, port, device, idx, id);
/* Register any analog stick input requests for
* this 'virtual' (core) port */
if ( (device == RETRO_DEVICE_ANALOG)
&& ( (idx == RETRO_DEVICE_INDEX_ANALOG_LEFT)
|| (idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT)))
input_st->analog_requested[port] = true;
#ifdef HAVE_BSV_MOVIE
if (BSV_MOVIE_IS_RECORDING())
#ifdef HAVE_REWIND
if (!state_manager_frame_is_reversed())
#endif
bsv_movie_push_input_event(
input_st->bsv_movie_state_handle,
port,
device,
idx,
id,
result);
#endif
#ifdef HAVE_GAME_AI
if (settings->bools.game_ai_override_p1 && port == 0)
result |= game_ai_input(port, device, idx, id, result);
if (settings->bools.game_ai_override_p2 && port == 1)
result |= game_ai_input(port, device, idx, id, result);
#endif
return result;
}
#ifdef HAVE_HID
void *hid_driver_get_data(void)
{
return (void*)input_driver_st.hid_data;
}
/* This is only to be called after we've invoked free() on the
* HID driver; the memory will have already been freed, so we need to
* reset the pointer.
*/
void hid_driver_reset_data(void) { input_driver_st.hid_data = NULL; }
/**
* config_get_hid_driver_options:
*
* Get an enumerated list of all HID driver names, separated by '|'.
*
* Returns: string listing of all HID driver names, separated by '|'.
**/
const char* config_get_hid_driver_options(void)
{
return char_list_new_special(STRING_LIST_INPUT_HID_DRIVERS, NULL);
}
/**
* input_hid_init_first:
*
* Finds first suitable HID driver and initializes.
*
* Returns: HID driver if found, otherwise NULL.
**/
const hid_driver_t *input_hid_init_first(void)
{
unsigned i;
input_driver_state_t *input_st = &input_driver_st;
for (i = 0; hid_drivers[i]; i++)
{
input_st->hid_data = hid_drivers[i]->init();
if (input_st->hid_data)
{
RARCH_LOG("[Input] Found HID driver: \"%s\".\n",
hid_drivers[i]->ident);
return hid_drivers[i];
}
}
return NULL;
}
#endif
void input_remapping_cache_global_config(void)
{
unsigned i;
settings_t *settings = config_get_ptr();
input_driver_state_t *input_st = &input_driver_st;
for (i = 0; i < MAX_USERS; i++)
{
/* Libretro device type is always set to
* RETRO_DEVICE_JOYPAD globally *unless*
* an override has been set via the command
* line interface */
unsigned device = RETRO_DEVICE_JOYPAD;
if (retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE, &i))
device = settings->uints.input_libretro_device[i];
input_st->remapping_cache.analog_dpad_mode[i] = settings->uints.input_analog_dpad_mode[i];
input_st->remapping_cache.libretro_device[i] = device;
}
input_st->remapping_cache.turbo_enable = settings->bools.input_turbo_enable;
input_st->remapping_cache.turbo_allow_dpad = settings->bools.input_turbo_allow_dpad;
input_st->remapping_cache.turbo_bind = settings->ints.input_turbo_bind;
input_st->remapping_cache.turbo_mode = settings->uints.input_turbo_mode;
input_st->remapping_cache.turbo_button = settings->uints.input_turbo_button;
input_st->remapping_cache.turbo_period = settings->uints.input_turbo_period;
input_st->remapping_cache.turbo_duty_cycle = settings->uints.input_turbo_duty_cycle;
}
void input_remapping_restore_global_config(bool clear_cache, bool restore_analog_dpad_mode)
{
unsigned i;
settings_t *settings = config_get_ptr();
input_driver_state_t *input_st = &input_driver_st;
if (!(input_st->flags & INP_FLAG_REMAPPING_CACHE_ACTIVE))
goto end;
for (i = 0; i < MAX_USERS; i++)
{
if (restore_analog_dpad_mode)
configuration_set_uint(settings,
settings->uints.input_analog_dpad_mode[i],
input_st->remapping_cache.analog_dpad_mode[i]);
configuration_set_uint(settings,
settings->uints.input_libretro_device[i],
input_st->remapping_cache.libretro_device[i]);
}
configuration_set_bool(settings,
settings->bools.input_turbo_enable,
input_st->remapping_cache.turbo_enable);
configuration_set_bool(settings,
settings->bools.input_turbo_allow_dpad,
input_st->remapping_cache.turbo_allow_dpad);
configuration_set_int(settings,
settings->ints.input_turbo_bind,
input_st->remapping_cache.turbo_bind);
configuration_set_uint(settings,
settings->uints.input_turbo_mode,
input_st->remapping_cache.turbo_mode);
configuration_set_uint(settings,
settings->uints.input_turbo_button,
input_st->remapping_cache.turbo_button);
configuration_set_uint(settings,
settings->uints.input_turbo_period,
input_st->remapping_cache.turbo_period);
configuration_set_uint(settings,
settings->uints.input_turbo_duty_cycle,
input_st->remapping_cache.turbo_duty_cycle);
end:
if (clear_cache)
input_st->flags &= ~INP_FLAG_REMAPPING_CACHE_ACTIVE;
}
void input_remapping_update_port_map(void)
{
unsigned i, j;
settings_t *settings = config_get_ptr();
unsigned port_map_index[MAX_USERS] = {0};
/* First pass: 'reset' port map */
for (i = 0; i < MAX_USERS; i++)
for (j = 0; j < (MAX_USERS + 1); j++)
settings->uints.input_remap_port_map[i][j] = MAX_USERS;
/* Second pass: assign port indices from
* 'input_remap_ports' */
for (i = 0; i < MAX_USERS; i++)
{
unsigned remap_port = settings->uints.input_remap_ports[i];
if (remap_port < MAX_USERS)
{
/* 'input_remap_port_map' provides a list of
* 'physical' ports for each 'virtual' port
* sampled in input_state().
* (Note: in the following explanation, port
* index starts from 0, rather than the frontend
* display convention of 1)
* For example - the following remap configuration
* will map input devices 0+1 to port 0, and input
* device 2 to port 1
* > input_remap_ports[0] = 0;
* input_remap_ports[1] = 0;
* input_remap_ports[2] = 1;
* This gives a port map of:
* > input_remap_port_map[0] = { 0, 1, MAX_USERS, ... };
* input_remap_port_map[1] = { 2, MAX_USERS, ... }
* input_remap_port_map[2] = { MAX_USERS, ... }
* ...
* A port map value of MAX_USERS indicates the end
* of the 'physical' port list */
settings->uints.input_remap_port_map[remap_port]
[port_map_index[remap_port]] = i;
port_map_index[remap_port]++;
}
}
}
void input_remapping_deinit(bool save_remap)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
if (runloop_st->name.remapfile)
{
if (save_remap)
input_remapping_save_file(runloop_st->name.remapfile);
free(runloop_st->name.remapfile);
}
runloop_st->name.remapfile = NULL;
runloop_st->flags &= ~(RUNLOOP_FLAG_REMAPS_CORE_ACTIVE
| RUNLOOP_FLAG_REMAPS_CONTENT_DIR_ACTIVE
| RUNLOOP_FLAG_REMAPS_GAME_ACTIVE);
}
void input_remapping_set_defaults(bool clear_cache)
{
unsigned i, j;
settings_t *settings = config_get_ptr();
for (i = 0; i < MAX_USERS; i++)
{
/* Button/keyboard remaps */
for (j = 0; j < RARCH_FIRST_CUSTOM_BIND; j++)
{
const struct retro_keybind *keybind = &input_config_binds[i][j];
configuration_set_uint(settings,
settings->uints.input_remap_ids[i][j],
keybind ? keybind->id : RARCH_UNMAPPED);
configuration_set_uint(settings,
settings->uints.input_keymapper_ids[i][j], RETROK_UNKNOWN);
}
/* Analog stick remaps */
for (j = RARCH_FIRST_CUSTOM_BIND; j < RARCH_ANALOG_BIND_LIST_END; j++)
configuration_set_uint(settings,
settings->uints.input_remap_ids[i][j], j);
/* Controller port remaps */
configuration_set_uint(settings,
settings->uints.input_remap_ports[i], i);
}
/* Need to call 'input_remapping_update_port_map()'
* whenever 'settings->uints.input_remap_ports'
* is modified */
input_remapping_update_port_map();
/* Restore 'global' settings that were cached on
* the last core init
* > Prevents remap changes from 'bleeding through'
* into the main config file */
input_remapping_restore_global_config(clear_cache, true);
}
void input_driver_collect_system_input(input_driver_state_t *input_st,
settings_t *settings, input_bits_t *current_bits)
{
rarch_joypad_info_t joypad_info;
input_driver_t *input = input_st->current_driver;
const input_device_driver_t *joypad = input_st->primary_joypad;
#ifdef HAVE_MFI
const input_device_driver_t
*sec_joypad = input_st->secondary_joypad;
#else
const input_device_driver_t
*sec_joypad = NULL;
#endif
unsigned block_delay = settings->uints.input_hotkey_block_delay;
uint8_t max_users = settings->uints.input_max_users;
uint8_t port = 0;
uint8_t hotkey_port = 0;
#ifdef HAVE_MENU
bool all_users_control_menu = settings->bools.input_all_users_control_menu;
bool display_kb = menu_input_dialog_get_display_kb();
bool menu_is_alive = (menu_state_get_ptr()->flags &
MENU_ST_FLAG_ALIVE) ? true : false;
bool menu_input_active = menu_is_alive &&
!(settings->bools.menu_unified_controls && !display_kb);
#endif
joypad_info.axis_threshold = settings->floats.input_axis_threshold;
/* Gather input from each (enabled) joypad */
for (port = 0; port < (int)max_users; port++)
{
const struct retro_keybind *binds_norm = &input_config_binds[port][RARCH_ENABLE_HOTKEY];
const struct retro_keybind *binds_auto = NULL;
joypad_info.joy_idx = settings->uints.input_joypad_index[port];
joypad_info.auto_binds = input_autoconf_binds[joypad_info.joy_idx];
binds_auto = &input_autoconf_binds[joypad_info.joy_idx][RARCH_ENABLE_HOTKEY];
#ifdef HAVE_MENU
if (menu_is_alive && joypad)
{
uint8_t s;
uint8_t a;
/* Read input from analog sticks according to settings. */
for (s = RETRO_DEVICE_INDEX_ANALOG_LEFT; s <= RETRO_DEVICE_INDEX_ANALOG_RIGHT; s++)
{
if ( (settings->bools.menu_disable_left_analog && s == RETRO_DEVICE_INDEX_ANALOG_LEFT)
|| (settings->bools.menu_disable_right_analog && s == RETRO_DEVICE_INDEX_ANALOG_RIGHT))
continue;
for (a = RETRO_DEVICE_ID_ANALOG_X; a <= RETRO_DEVICE_ID_ANALOG_Y; a++)
{
int16_t ret = input_joypad_analog_axis(
ANALOG_DPAD_NONE,
settings->floats.input_analog_deadzone,
settings->floats.input_analog_sensitivity,
joypad,
&joypad_info,
s,
a,
(*input_st->libretro_input_binds[port]));
if (ret)
{
bool playlist = false;
/* Replace right analog stick navigation in playlists to thumbnail cycling. */
if (s == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
{
menu_entry_t entry;
MENU_ENTRY_INITIALIZE(entry);
menu_entry_get(&entry, 0, 0, NULL, true);
switch (entry.type)
{
case FILE_TYPE_RPL_ENTRY:
case FILE_TYPE_PLAYLIST_ENTRY:
case FILE_TYPE_PLAIN:
case FILE_TYPE_RDB:
playlist = true;
break;
default:
break;
}
}
if (a == RETRO_DEVICE_ID_ANALOG_Y && (float)ret / 0x7fff < -joypad_info.axis_threshold)
{
if (playlist)
BIT256_SET_PTR(current_bits, RARCH_ANALOG_RIGHT_Y_MINUS);
else
BIT256_SET_PTR(current_bits, RETRO_DEVICE_ID_JOYPAD_UP);
}
else if (a == RETRO_DEVICE_ID_ANALOG_Y && (float)ret / 0x7fff > joypad_info.axis_threshold)
{
if (playlist)
BIT256_SET_PTR(current_bits, RARCH_ANALOG_RIGHT_Y_PLUS);
else
BIT256_SET_PTR(current_bits, RETRO_DEVICE_ID_JOYPAD_DOWN);
}
if (a == RETRO_DEVICE_ID_ANALOG_X && (float)ret / 0x7fff < -joypad_info.axis_threshold)
{
if (playlist)
BIT256_SET_PTR(current_bits, RARCH_ANALOG_RIGHT_X_MINUS);
else
BIT256_SET_PTR(current_bits, RETRO_DEVICE_ID_JOYPAD_LEFT);
}
else if (a == RETRO_DEVICE_ID_ANALOG_X && (float)ret / 0x7fff > joypad_info.axis_threshold)
{
if (playlist)
BIT256_SET_PTR(current_bits, RARCH_ANALOG_RIGHT_X_PLUS);
else
BIT256_SET_PTR(current_bits, RETRO_DEVICE_ID_JOYPAD_RIGHT);
}
}
}
}
}
#endif /* HAVE_MENU */
if (settings->bools.input_hotkey_follows_player1)
{
/* Hotkeys are bound to player 1 (the first user mapped to core port 0),
* even if player 1 is remapped to a different user. */
hotkey_port = settings->uints.input_remap_port_map[0][0];
if (hotkey_port >= MAX_USERS)
hotkey_port = 0;
}
input_keys_pressed(
port,
hotkey_port,
#ifdef HAVE_MENU
menu_is_alive,
#else
false,
#endif
block_delay,
current_bits,
(const retro_keybind_set *)input_config_binds,
binds_norm,
binds_auto,
joypad,
sec_joypad,
&joypad_info,
settings->bools.input_hotkey_device_merge);
#ifdef HAVE_MENU
if (menu_is_alive)
{
if (!all_users_control_menu)
break;
}
#endif /* HAVE_MENU */
}
#ifdef HAVE_MENU
if (menu_input_active)
{
/* Gather keyboard input, if enabled
* Note: Keyboard input always read from
* port 0 */
if (!display_kb && input && input->input_state)
{
struct menu_state *menu_st = menu_state_get_ptr();
bool swap_ok_cancel_buttons = settings->bools.input_menu_swap_ok_cancel_buttons;
unsigned i;
unsigned ids[][2] =
{
{RETROK_RETURN, RETRO_DEVICE_ID_JOYPAD_A },
{RETROK_BACKSPACE, RETRO_DEVICE_ID_JOYPAD_B },
{RETROK_DELETE, RETRO_DEVICE_ID_JOYPAD_Y },
{RETROK_SLASH, RETRO_DEVICE_ID_JOYPAD_X },
{RETROK_SPACE, RETRO_DEVICE_ID_JOYPAD_START },
{RETROK_RSHIFT, RETRO_DEVICE_ID_JOYPAD_SELECT },
{RETROK_UP, RETRO_DEVICE_ID_JOYPAD_UP },
{RETROK_DOWN, RETRO_DEVICE_ID_JOYPAD_DOWN },
{RETROK_LEFT, RETRO_DEVICE_ID_JOYPAD_LEFT },
{RETROK_RIGHT, RETRO_DEVICE_ID_JOYPAD_RIGHT },
{RETROK_PAGEUP, RETRO_DEVICE_ID_JOYPAD_L },
{RETROK_PAGEDOWN, RETRO_DEVICE_ID_JOYPAD_R },
{RETROK_HOME, RETRO_DEVICE_ID_JOYPAD_L3 },
{RETROK_END, RETRO_DEVICE_ID_JOYPAD_R3 },
/* Extra keys read regardless of 'enable_hotkey' */
{0, RARCH_QUIT_KEY }, /* 14 */
{0, RARCH_FULLSCREEN_TOGGLE_KEY },
{0, RARCH_UI_COMPANION_TOGGLE },
{0, RARCH_FPS_TOGGLE },
{0, RARCH_NETPLAY_HOST_TOGGLE },
{0, RARCH_BIND_LIST_END_NULL },
};
ids[14][0] = input_config_binds[0][RARCH_QUIT_KEY].key;
ids[15][0] = input_config_binds[0][RARCH_FULLSCREEN_TOGGLE_KEY].key;
ids[16][0] = input_config_binds[0][RARCH_UI_COMPANION_TOGGLE].key;
ids[17][0] = input_config_binds[0][RARCH_FPS_TOGGLE].key;
ids[18][0] = input_config_binds[0][RARCH_NETPLAY_HOST_TOGGLE].key;
ids[19][0] = RETROK_ESCAPE;
/* Escape cancels dialogs */
if (menu_st && menu_st->driver_data && *menu_st->driver_data->menu_state_msg)
ids[19][1] = (swap_ok_cancel_buttons)
? RETRO_DEVICE_ID_JOYPAD_A : RETRO_DEVICE_ID_JOYPAD_B;
if (swap_ok_cancel_buttons)
{
ids[0][1] = RETRO_DEVICE_ID_JOYPAD_B;
ids[1][1] = RETRO_DEVICE_ID_JOYPAD_A;
}
for (i = 0; i < ARRAY_SIZE(ids); i++)
{
if (ids[i][0] && input->input_state(
input_st->current_data,
joypad,
sec_joypad,
&joypad_info,
(const retro_keybind_set *)input_config_binds,
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
0,
RETRO_DEVICE_KEYBOARD, 0, ids[i][0]))
{
/* Wait for release when closing dialogs with Escape,
* otherwise quit hotkey will also get triggered */
if (ids[i][0] == RETROK_ESCAPE && ids[19][1] != RARCH_BIND_LIST_END_NULL)
input_st->flags |= INP_FLAG_WAIT_INPUT_RELEASE;
BIT256_SET_PTR(current_bits, ids[i][1]);
}
}
}
else if (display_kb && input && input->input_state)
{
/* Allow character map switches,
* and set RetroPad Select bit when pressing Escape
* in order to clear the input window and close it. */
unsigned i;
unsigned ids[][2] =
{
{RETROK_PAGEUP, RETRO_DEVICE_ID_JOYPAD_L },
{RETROK_PAGEDOWN, RETRO_DEVICE_ID_JOYPAD_R },
{RETROK_ESCAPE, RETRO_DEVICE_ID_JOYPAD_SELECT },
};
for (i = 0; i < ARRAY_SIZE(ids); i++)
{
if (input->input_state(
input_st->current_data,
joypad,
sec_joypad,
&joypad_info,
(const retro_keybind_set *)input_config_binds,
!!(input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED),
0,
RETRO_DEVICE_KEYBOARD, 0, ids[i][0]))
BIT256_SET_PTR(current_bits, ids[i][1]);
}
}
}
else
#endif /* HAVE_MENU */
{
#if defined(HAVE_ACCESSIBILITY) && defined(HAVE_TRANSLATE)
if (settings->bools.ai_service_enable)
{
int i;
input_st->gamepad_input_override = 0;
for (i = 0; i < MAX_USERS; i++)
{
/* Set gamepad input override */
if (input_st->ai_gamepad_state[i] == 2)
input_st->gamepad_input_override |= (1 << i);
input_st->ai_gamepad_state[i] = 0;
}
}
#endif /* defined(HAVE_ACCESSIBILITY) && defined(HAVE_TRANSLATE) */
}
}
#if defined(HAVE_MENU) && defined(HAVE_ACCESSIBILITY)
static const char *accessibility_lut_name(char key)
{
switch (key)
{
case '`':
return "tilde";
case '!':
return "exclamation point";
case '@':
return "at sign";
case '#':
return "hash sign";
case '$':
return "dollar sign";
case '%':
return "percent sign";
case '^':
return "carrot";
case '&':
return "ampersand";
case '*':
return "asterisk";
case '(':
return "left bracket";
case ')':
return "right bracket";
case '-':
return "minus";
case '_':
return "underscore";
case '=':
return "equals";
case '+':
return "plus";
case '[':
return "left square bracket";
case '{':
return "left curl bracket";
case ']':
return "right square bracket";
case '}':
return "right curl bracket";
case '\\':
return "back slash";
case '|':
return "pipe";
case ';':
return "semicolon";
case ':':
return "colon";
case '\'':
return "single quote";
case '\"':
return "double quote";
case ',':
return "comma";
case '<':
return "left angle bracket";
case '.':
return "period";
case '>':
return "right angle bracket";
case '/':
return "front slash";
case '?':
return "question mark";
case ' ':
return "space";
default:
break;
}
return NULL;
}
#endif
void input_keyboard_event(bool down, unsigned code,
uint32_t character, uint16_t mod, unsigned device)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
retro_keyboard_event_t
*key_event = &runloop_st->key_event;
input_driver_state_t
*input_st = &input_driver_st;
#ifdef HAVE_ACCESSIBILITY
access_state_t *access_st = access_state_get_ptr();
settings_t *settings = config_get_ptr();
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed
= settings->uints.accessibility_narrator_speech_speed;
#endif
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
/* If screensaver is active, then it should be
* disabled if:
* - Key is down AND
* - OSK is active, OR:
* - Key is *not* mapped to RetroPad input (these
* inputs are handled in menu_event() - if we
* allow mapped RetroPad keys to toggle off
* the screensaver, then we end up with a 'duplicate'
* input that will trigger unwanted menu action)
* - For extra amusement, a number of keyboard keys
* are hard-coded to RetroPad inputs (while the menu
* is running) in such a way that they cannot be
* detected via the regular 'keyboard_mapping_bits'
* record. We therefore have to check each of these
* explicitly...
* Otherwise, input is ignored whenever screensaver
* is active */
if (menu_st->flags & MENU_ST_FLAG_SCREENSAVER_ACTIVE)
{
if ( (down)
&& (code != RETROK_UNKNOWN)
&& (menu_input_dialog_get_display_kb()
|| !((code == RETROK_SPACE) /* RETRO_DEVICE_ID_JOYPAD_START */
|| (code == RETROK_SLASH) /* RETRO_DEVICE_ID_JOYPAD_X */
|| (code == RETROK_RSHIFT) /* RETRO_DEVICE_ID_JOYPAD_SELECT */
|| (code == RETROK_RIGHT) /* RETRO_DEVICE_ID_JOYPAD_RIGHT */
|| (code == RETROK_LEFT) /* RETRO_DEVICE_ID_JOYPAD_LEFT */
|| (code == RETROK_DOWN) /* RETRO_DEVICE_ID_JOYPAD_DOWN */
|| (code == RETROK_UP) /* RETRO_DEVICE_ID_JOYPAD_UP */
|| (code == RETROK_PAGEUP) /* RETRO_DEVICE_ID_JOYPAD_L */
|| (code == RETROK_PAGEDOWN) /* RETRO_DEVICE_ID_JOYPAD_R */
|| (code == RETROK_BACKSPACE) /* RETRO_DEVICE_ID_JOYPAD_B */
|| (code == RETROK_RETURN) /* RETRO_DEVICE_ID_JOYPAD_A */
|| (code == RETROK_DELETE) /* RETRO_DEVICE_ID_JOYPAD_Y */
|| (BIT512_GET(input_st->keyboard_mapping_bits, code)))))
{
menu_st->flags &= ~MENU_ST_FLAG_SCREENSAVER_ACTIVE;
menu_st->input_last_time_us = menu_st->current_time_us;
if (menu_st->driver_ctx->environ_cb)
menu_st->driver_ctx->environ_cb(MENU_ENVIRON_DISABLE_SCREENSAVER,
NULL, menu_st->userdata);
}
return;
}
if (down)
menu_st->input_last_time_us = menu_st->current_time_us;
#ifdef HAVE_ACCESSIBILITY
if ( menu_input_dialog_get_display_kb()
&& down
&& is_accessibility_enabled(accessibility_enable,
access_st->enabled))
{
if (code != 303 && code != 0)
{
char say_char[2];
char c = (char)character;
say_char[0] = c;
say_char[1] = '\0';
if (character == 127 || character == 8)
accessibility_speak_priority(accessibility_enable,
accessibility_narrator_speech_speed, "backspace", 10);
else
{
const char *lut_name = accessibility_lut_name(c);
if (lut_name)
accessibility_speak_priority(
accessibility_enable,
accessibility_narrator_speech_speed,
lut_name, 10);
else if (character != 0)
accessibility_speak_priority(
accessibility_enable,
accessibility_narrator_speech_speed,
say_char, 10);
}
}
}
#endif
#endif
if (input_st->flags & INP_FLAG_DEFERRED_WAIT_KEYS)
{
if (down)
return;
input_st->keyboard_press_cb = NULL;
input_st->keyboard_press_data = NULL;
input_st->flags &= ~(INP_FLAG_KB_MAPPING_BLOCKED
| INP_FLAG_DEFERRED_WAIT_KEYS
);
}
else if (input_st->keyboard_press_cb)
{
if (!down || code == RETROK_UNKNOWN)
return;
if (input_st->keyboard_press_cb(input_st->keyboard_press_data, code))
return;
input_st->flags |= INP_FLAG_DEFERRED_WAIT_KEYS;
}
else if (input_st->keyboard_line.enabled)
{
input_keyboard_line_t *line = &input_st->keyboard_line;
if (!down)
return;
switch (code)
{
case RETROK_LEFT:
if (line->ptr && line->buffer)
{
line->ptr--;
while (line->ptr && IS_UTF8_CONTINUATION(line->buffer[line->ptr]))
line->ptr--;
if (mod & RETROKMOD_CTRL)
while (line->ptr &&
(ISSPACE(line->buffer[line->ptr]) || !ISSPACE(line->buffer[line->ptr - 1])))
line->ptr--;
}
return;
case RETROK_RIGHT:
if (line->buffer && line->ptr < line->size)
{
line->ptr++;
while (line->ptr < line->size && IS_UTF8_CONTINUATION(line->buffer[line->ptr]))
line->ptr++;
if (mod & RETROKMOD_CTRL)
while (line->ptr < line->size &&
(ISSPACE(line->buffer[line->ptr]) || !ISSPACE(line->buffer[line->ptr - 1])))
line->ptr++;
}
return;
case RETROK_UP:
line->ptr = 0;
return;
case RETROK_DOWN:
line->ptr = line->size;
return;
default:
break;
}
if (!input_keyboard_line_event(input_st, line, character))
return;
/* Line is complete, can free it now. */
input_keyboard_line_free(input_st);
/* Unblock all hotkeys. */
input_st->flags &= ~INP_FLAG_KB_MAPPING_BLOCKED;
}
else
{
if (code == RETROK_UNKNOWN)
return;
/* Check if keyboard events should be blocked when
* pressing hotkeys and RetroPad binds, but
* - not with Game Focus
* - not from keyboard device type mappings
* - not from overlay keyboard input
* - with 'enable_hotkey' modifier set and unpressed.
*
* Also do not block key up events, because keys will
* get stuck if Game Focus key is also pressing a key. */
if ( down
&& !input_st->game_focus_state.enabled
&& BIT512_GET(input_st->keyboard_mapping_bits, code)
&& device != RETRO_DEVICE_POINTER)
{
unsigned j;
settings_t *settings = config_get_ptr();
unsigned max_users = settings->uints.input_max_users;
bool hotkey_pressed = (input_st->input_hotkey_block_counter > 0);
bool block_key_event = false;
/* Loop enabled ports for keycode dupes. */
for (j = 0; j < max_users; j++)
{
unsigned k;
unsigned hotkey_code = input_config_binds[0][RARCH_ENABLE_HOTKEY].key;
/* Block hotkey key events based on 'enable_hotkey' modifier,
* and only when modifier is a keyboard key. */
if ( j == 0
&& !block_key_event
&& !( !hotkey_pressed
&& hotkey_code != RETROK_UNKNOWN
&& hotkey_code != code))
{
for (k = RARCH_FIRST_META_KEY; k < RARCH_BIND_LIST_END; k++)
{
if (input_config_binds[j][k].key == code)
{
block_key_event = true;
break;
}
}
}
/* RetroPad blocking needed only when emulated
* device type is active. */
if ( input_config_get_device(j)
&& !block_key_event)
{
for (k = 0; k < RARCH_FIRST_META_KEY; k++)
{
if (input_config_binds[j][k].key == code)
{
block_key_event = true;
break;
}
}
}
}
/* No blocking when event comes from emulated keyboard device type */
if (MAPPER_GET_KEY(&input_st->mapper, code))
block_key_event = false;
if (block_key_event)
return;
}
if (*key_event)
{
if (*key_event == runloop_st->frontend_key_event)
{
#ifdef HAVE_BSV_MOVIE
if (BSV_MOVIE_IS_RECORDING())
#ifdef HAVE_REWIND
if (!state_manager_frame_is_reversed())
#endif
bsv_movie_push_key_event(
input_st->bsv_movie_state_handle, down, mod,
code, character);
#endif
}
(*key_event)(down, code, character, mod);
}
}
}