/**
 *  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/>.
 **/

#define _USE_MATH_DEFINES
#include <math.h>
#include <string/stdstring.h>
#include <encodings/utf.h>
#include <clamping.h>
#include <retro_assert.h>

#include "input_driver.h"
#include "input_keymaps.h"
#include "input_remapping.h"
#include "input_osk.h"
#include "input_types.h"

#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 "../driver.h"
#include "../retroarch.h"
#include "../verbosity.h"
#include "../configuration.h"
#include "../list_special.h"
#include "../performance_counters.h"
#ifdef HAVE_BSV_MOVIE
#include "../tasks/task_content.h"
#endif
#include "../tasks/tasks_internal.h"

#define HOLD_BTN_DELAY_SEC 2

/* Depends on ASCII character values */
#define ISPRINT(c) (((int)(c) >= ' ' && (int)(c) <= '~') ? 1 : 0)

#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)

/**
 * 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)) \
)

/**************************************/
/* 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, /* 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_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
   &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 __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)
   &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
   &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("[Joypad]: 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;

   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 (   (current_driver = input_driver_st.current_driver)
       &&  current_driver->set_sensor_state)
   {
      void *current_data = input_driver_st.current_data;
      return current_driver->set_sensor_state(current_data,
            port, action, rate);
   }

   return false;
}

/**************************************/

float input_driver_get_sensor(
         unsigned port, bool sensors_enable, unsigned id)
{
   if (input_driver_st.current_data)
   {
      const input_driver_t *current_driver = input_driver_st.current_driver;
      if (sensors_enable && current_driver->get_sensor_input)
      {
         void *current_data = input_driver_st.current_data;
         return current_driver->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("[Joypad]: Found joypad driver: \"%s\".\n",
                     joypad_drivers[i]->ident);
               return joypad_drivers[i];
            }
         }
      }
   }

   return input_joypad_init_first(data); /* fall back to first available driver */
}

bool input_driver_button_combo(
      unsigned mode,
      retro_time_t current_time,
      input_bits_t* p_input)
{
   retro_assert(p_input  != NULL);

   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:
         {
            rarch_timer_t *timer = &input_driver_st.combo_timers[INPUT_COMBO_HOLD_START];

            if (!BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_START))
            {
               /* timer only runs while start is held down */
               timer->timer_end   = true;
               timer->timer_begin = false;
               timer->timeout_end = 0;
               return false;
            }

            /* User started holding down the start button, start the timer */
            if (!timer->timer_begin)
            {
               uint64_t current_usec = cpu_features_get_time_usec();
               timer->timeout_us     = HOLD_BTN_DELAY_SEC * 1000000;
               timer->current        = current_usec;
               timer->timeout_end    = timer->current + 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 */
               timer->timer_end   = true;
               timer->timer_begin = false;
               timer->timeout_end = 0;
               return true;
            }

         }
         break;
      case INPUT_COMBO_HOLD_SELECT:
         {
            rarch_timer_t *timer = &input_driver_st.combo_timers[INPUT_COMBO_HOLD_SELECT];

            if (!BIT256_GET_PTR(p_input, RETRO_DEVICE_ID_JOYPAD_SELECT))
            {
               /* timer only runs while select is held down */
               timer->timer_end   = true;
               timer->timer_begin = false;
               timer->timeout_end = 0;
               return false;
            }

            /* user started holding down the select button, start the timer */
            if (!timer->timer_begin)
            {
               uint64_t current_usec = cpu_features_get_time_usec();
               timer->timeout_us     = HOLD_BTN_DELAY_SEC * 1000000;
               timer->current        = current_usec;
               timer->timeout_end    = timer->current + 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))
            {
               /* select has been held down long enough,
                * stop timer and enter menu */
               timer->timer_end   = true;
               timer->timer_begin = false;
               timer->timeout_end = 0;
               return true;
            }
         }
         break;
      default:
      case INPUT_COMBO_NONE:
         break;
   }

   return false;
}

static int16_t input_state_wrap(
      input_driver_t *current_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)
{
   int16_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
      {
         /* Do a bitwise OR to combine both input
          * states together */
         if (binds[_port][id].valid)
         {
            /* Auto-binds are per joypad, not per user. */
            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;
            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))
                      / 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))
                      / 0x8000) > axis_threshold)
                  return 1;
            }
         }
      }
   }

   if (current_input && current_input->input_state)
      ret |= current_input->input_state(
            data,
            joypad,
            sec_joypad,
            joypad_info,
            binds,
            keyboard_mapping_blocked,
            _port,
            device,
            idx,
            id);
   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->axis(port, joyaxis) : 0;

   if (input_analog_deadzone)
   {
      /* if analog value is below the deadzone, ignore it
       * normal magnitude is calculated radially for analog sticks
       * and linearly for analog buttons */
      if (normal_mag <= input_analog_deadzone)
         return 0;

      /* due to the way normal_mag is calculated differently for buttons and
       * sticks, this results in either a radial scaled deadzone for sticks
       * or linear scaled deadzone for analog buttons */
      val = val * MAX(1.0f,(1.0f / normal_mag)) * MIN(1.0f,
            ((normal_mag - input_analog_deadzone)
          / (1.0f - input_analog_deadzone)));
   }

   if (input_analog_sensitivity != 1.0f)
   {
      float normalized = (1.0f / 0x7fff) * val;
      int      new_val = 0x7fff * normalized  *
         input_analog_sensitivity;

      if (new_val > 0x7fff)
         return 0x7fff;
      else if (new_val < -0x7fff)
         return -0x7fff;

      return 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;
   uint32_t axis                    = (bind->joyaxis == AXIS_NONE)
      ? joypad_info->auto_binds[ident].joyaxis
      : bind->joyaxis;

   /* Analog button. */
   if (input_analog_deadzone)
   {
      int16_t mult = 0;
      if (axis != AXIS_NONE)
         if ((mult = drv->axis(
                     joypad_info->joy_idx, axis)) != 0)
            normal_mag   = fabs((1.0f / 0x7fff) * mult);
   }

   /* 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,
            joypad_info->joy_idx, axis, normal_mag))) == 0)
   {
      uint16_t key = (bind->joykey == NO_BTN)
         ? joypad_info->auto_binds[ident].joykey
         : bind->joykey;

      if (drv->button(joypad_info->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;
      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;

   {
      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;
         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)
            x                     = drv->axis(
                  joypad_info->joy_idx, x_axis_plus);
         if (x_axis_minus != AXIS_NONE)
            x                    += drv->axis(joypad_info->joy_idx,
                  x_axis_minus);
         if (y_axis_plus != AXIS_NONE)
            y                     = drv->axis(
                  joypad_info->joy_idx, y_axis_plus);
         if (y_axis_minus != AXIS_NONE)
            y                    += drv->axis(
                  joypad_info->joy_idx, y_axis_minus);
         normal_mag               = (1.0f / 0x7fff) * sqrt(x * x + y * y);
      }

      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(joypad_info->joy_idx, key_plus))
         res  = 0x7fff;
      if (drv->button(joypad_info->joy_idx, key_minus))
         res += -0x7fff;
   }

   return res;
}

void input_keyboard_line_append(
      struct input_keyboard_line *keyboard_line,
      const char *word, size_t len)
{
   size_t i;
   char *newbuf                = (char*)realloc(
         keyboard_line->buffer,
         keyboard_line->size + len * 2);

   if (!newbuf)
      return;

   memmove(
         newbuf + keyboard_line->ptr + len,
         newbuf + keyboard_line->ptr,
         keyboard_line->size - keyboard_line->ptr + len);

   for (i = 0; i < len; i++)
   {
      newbuf[keyboard_line->ptr]= word[i];
      keyboard_line->ptr++;
      keyboard_line->size++;
   }

   newbuf[keyboard_line->size]  = '\0';

   keyboard_line->buffer        = newbuf;
}

const char **input_keyboard_start_line(
      void *userdata,
      struct input_keyboard_line *keyboard_line,
      input_keyboard_line_complete_t cb)
{
   keyboard_line->buffer    = NULL;
   keyboard_line->ptr       = 0;
   keyboard_line->size      = 0;
   keyboard_line->cb        = cb;
   keyboard_line->userdata  = userdata;
   keyboard_line->enabled   = true;

   return (const char**)&keyboard_line->buffer;
}

#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("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)
      goto error;

   handle->net_fd[user] = fd;

   if (!socket_nonblock(handle->net_fd[user]))
      goto error;

   if (!socket_bind(handle->net_fd[user], res))
   {
      RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_BIND_SOCKET));
      goto error;
   }

   freeaddrinfo_retro(res);
   return true;

error:
   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:
         input_state->buttons[user] &= ~(1 << msg->id);
         if (msg->state)
            input_state->buttons[user] |= 1 << msg->id;
         break;
      case RETRO_DEVICE_ANALOG:
         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)
{
   unsigned network_remote_base_port = settings->uints.network_remote_base_port;
   return input_remote_new(
         settings,
         network_remote_base_port,
         num_active_users);
}
#endif

#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_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(port, RETRO_DEVICE_JOYPAD, 0, id))
                     {
                        /* We need ALL of the inputs to be active,
                         * abort. */
                        desc->updated = 0;
                        return false;
                     }

                     desc->updated    = 1;
                  }

                  bank_mask >>= 1;
                  ++id;
               }
            }

            return (desc->updated != 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->updated        |= (ol_state->analog[index_offset] |
                  ol_state->analog[index_offset + 1]);
         }
         else
         {
            unsigned index        = (desc->type == OVERLAY_TYPE_ANALOG_RIGHT) ?
               RETRO_DEVICE_INDEX_ANALOG_RIGHT : RETRO_DEVICE_INDEX_ANALOG_LEFT;
            float analog_x        = input_state_internal(port, RETRO_DEVICE_ANALOG,
                  index, RETRO_DEVICE_ID_ANALOG_X);
            float analog_y        = input_state_internal(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);
         }

         return (desc->updated != 0);

      case OVERLAY_TYPE_DPAD_AREA:
      case OVERLAY_TYPE_ABXY_AREA:
         return (desc->updated != 0);

      case OVERLAY_TYPE_KEYBOARD:
         if (ol_state ?
               OVERLAY_GET_KEY(ol_state, desc->retro_key_idx) :
               input_state_internal(port, RETRO_DEVICE_KEYBOARD, 0, desc->retro_key_idx))
         {
            desc->updated = 1;
            return true;
         }
         break;

      default:
         break;
   }

   return false;
}

static bool input_overlay_add_inputs(input_overlay_t *ol,
      bool show_touched, unsigned port)
{
   size_t i;
   bool button_pressed             = false;
   input_overlay_state_t *ol_state = &ol->overlay_state;

   if (!ol_state)
      return 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,
            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_state_get_ptr();

   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, input_bits_t *out,
      float x_dist, float y_dist)
{
   overlay_eightway_config_t *eightway = desc->eightway_config;
   uint32_t *data;
   float abs_slope;

   x_dist /= desc->range_x;
   y_dist /= desc->range_y;

   if (x_dist == 0.0f)
      x_dist = 0.0001f;
   abs_slope = fabs(y_dist / x_dist);

   if (x_dist > 0.0f)
   {
      if (y_dist < 0.0f)
      {
         /* Q1 */
         if (abs_slope > *eightway->slope_high)
            data = eightway->up.data;
         else if (abs_slope < *eightway->slope_low)
            data = eightway->right.data;
         else
            data = eightway->up_right.data;
      }
      else
      {
         /* Q4 */
         if (abs_slope > *eightway->slope_high)
            data = eightway->down.data;
         else if (abs_slope < *eightway->slope_low)
            data = eightway->right.data;
         else
            data = eightway->down_right.data;
      }
   }
   else
   {
      if (y_dist < 0.0f)
      {
         /* Q2 */
         if (abs_slope > *eightway->slope_high)
            data = eightway->up.data;
         else if (abs_slope < *eightway->slope_low)
            data = eightway->left.data;
         else
            data = eightway->up_left.data;
      }
      else
      {
         /* Q3 */
         if (abs_slope > *eightway->slope_high)
            data = eightway->down.data;
         else if (abs_slope < *eightway->slope_low)
            data = eightway->left.data;
         else
            data = eightway->down_left.data;
      }
   }

   bits_or_bits(out->data, data, CUSTOM_BINDS_U32_COUNT);
}

/**
 * inside_hitbox:
 * @desc                  : Overlay descriptor handle.
 * @x                     : X coordinate value.
 * @y                     : Y coordinate value.
 *
 * 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 inside_hitbox(const struct overlay_desc *desc, float x, float y)
{
   switch (desc->hitbox)
   {
      case OVERLAY_HITBOX_RADIAL:
      {
         /* Ellipse. */
         float x_dist  = (x - desc->x_hitbox) / desc->range_x_mod;
         float y_dist  = (y - desc->y_hitbox) / desc->range_y_mod;
         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) <= desc->range_x_mod) &&
            (fabs(y - desc->y_hitbox) <= desc->range_y_mod);
      case OVERLAY_HITBOX_NONE:
	 break;
   }
   return false;
}

/**
 * input_overlay_poll:
 * @out                   : Polled output data.
 * @ptr_idx               : Input pointer index.
 * @norm_x                : Normalized X coordinate.
 * @norm_y                : Normalized Y coordinate.
 *
 * Polls input overlay.
 *
 * @norm_x and @norm_y are the result of
 * input_translate_coord_viewport().
 **/
static void input_overlay_poll(
      input_overlay_t *ol,
      input_overlay_state_t *out,
      unsigned ptr_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;

   /* 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];

      if (!desc || !inside_hitbox(desc, x, y))
         continue;

      /* Check for exclusive hitbox, which blocks other input.
       * range_mod_exclusive has priority over exclusive. */
      if ((desc->flags & OVERLAY_DESC_RANGE_MOD_EXCLUSIVE)
            && desc->range_x_mod != desc->range_x_hitbox)
         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++)
            BIT16_CLEAR(descs[j].updated, ptr_idx);
      }

      BIT16_SET(desc->updated, ptr_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:
            input_overlay_get_eightway_state(
                  desc, &out->buttons, x_dist, y_dist);
            break;
         case OVERLAY_TYPE_ANALOG_RIGHT:
            base = 2;
            /* fall-through */
         default:
            {
               float x_val           = x_dist / desc->range_x;
               float y_val           = y_dist / desc->range_y;
               float x_val_sat       = x_val / desc->analog_saturate_pct;
               float 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;
            }
            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;
      }
   }

   if (!bits_any_set(out->buttons.data, ARRAY_SIZE(out->buttons.data)))
      ol->flags &= ~INPUT_OVERLAY_BLOCKED;
   else if (ol->flags & INPUT_OVERLAY_BLOCKED)
      memset(out, 0, sizeof(*out));
}

/**
 * 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 the range modifiers for pressed/unpressed regions
 * and alpha mods.
 **/
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];

      desc->range_x_mod = desc->range_x_hitbox;
      desc->range_y_mod = desc->range_y_hitbox;

      if (desc->updated != 0)
      {
         /* If pressed this frame, change the hitbox. */
         desc->range_x_mod *= desc->range_mod;
         desc->range_y_mod *= desc->range_mod;

         if (show_input && desc->image.pixels)
         {
            if (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->updated = 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_y_mod = desc->range_y_hitbox;
}

/**
 * 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 X separation is permitted, move elements
          * horizontally towards the edges of the screen */
         if (!(ol->flags & OVERLAY_BLOCK_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 Y separation is permitted and display has
          * a *landscape* orientation, move elements
          * vertically towards the edges of the screen
          * > Portrait overlays typically have all elements
          *   below the centre line, so Y separation
          *   provides no real benefit */
         if ((display_aspect_ratio > 1.0f) &&
             !(ol->flags & OVERLAY_BLOCK_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_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->range_x_mod = desc->range_x_hitbox;
      desc->range_y_mod = desc->range_y_hitbox;
      desc->updated     = 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;

   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_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++)
   {
      image_texture_free(&overlay->descs[i].image);
      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;
   image_texture_free(&overlay->image);
}

/**
 * input_overlay_free:
 * @ol                    : Overlay handle.
 *
 * Frees overlay handle.
 **/
static void input_overlay_free(input_overlay_t *ol)
{
   if (!ol)
      return;

   input_overlay_free_overlays(ol);

   if (ol->iface->enable)
      ol->iface->enable(ol->iface_data, false);

   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 (!string_is_empty(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 (!string_is_empty(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_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;
   uint16_t key_mod                        = 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_driver_t *current_input           = 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 osk_state_changed                  = false;
   unsigned touch_count                    = 0;
   static unsigned old_touch_count;

   if (!ol_state)
      return;

   memcpy(&old_ol_state, ol_state,
         sizeof(old_ol_state));
   memset(ol_state, 0, sizeof(*ol_state));

   if (current_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;

      for (i = 0;
            current_input->input_state(
               input_data,
               joypad,
               sec_joypad,
               &joypad_info,
               NULL,
               keyboard_mapping_blocked,
               0,
               device,
               i,
               RETRO_DEVICE_ID_POINTER_PRESSED);
            i++)
      {
         input_overlay_state_t polled_data;
         int16_t x = current_input->input_state(
               input_data,
               joypad,
               sec_joypad,
               &joypad_info,
               NULL,
               keyboard_mapping_blocked,
               0,
               device,
               i,
               RETRO_DEVICE_ID_POINTER_X);
         int16_t y = current_input->input_state(
               input_data,
               joypad,
               sec_joypad,
               &joypad_info,
               NULL,
               keyboard_mapping_blocked,
               0,
               device,
               i,
               RETRO_DEVICE_ID_POINTER_Y);

         memset(&polled_data, 0, sizeof(struct input_overlay_state));

         if (ol->flags & INPUT_OVERLAY_ENABLE)
            input_overlay_poll(ol, &polled_data, i, x, y, touch_scale);
         else
            ol->flags &= ~INPUT_OVERLAY_BLOCKED;

         bits_or_bits(ol_state->buttons.data,
               polled_data.buttons.data,
               ARRAY_SIZE(polled_data.buttons.data));

         for (j = 0; j < 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];
      }

      touch_count = i;
   }

   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 = 0; i < ARRAY_SIZE(ol_state->keys); i++)
   {
      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)))
               input_keyboard_event(new_bits & (1 << j),
                     i * 32 + j, 0, 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;
      }

      default:
         break;
   }

   button_pressed = input_overlay_add_inputs(ol,
         (input_overlay_show_inputs == OVERLAY_SHOW_INPUT_TOUCHED),
         input_overlay_show_inputs_port);

   if (button_pressed)
      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 || 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 (     current_input->keypress_vibrate
         && settings->bools.vibrate_on_keypress
         && touch_count && touch_count >= old_touch_count
         && !(ol->flags & INPUT_OVERLAY_BLOCKED))
   {
      if (     osk_state_changed
            || bits_any_different(
                     ol_state->buttons.data,
                     old_ol_state.buttons.data,
                     ARRAY_SIZE(old_ol_state.buttons.data))
         )
         current_input->keypress_vibrate();
   }

   old_touch_count = touch_count;
}
#endif

/**
 * input_config_translate_str_to_rk:
 * @str                            : String to translate to key ID.
 *
 * Translates tring representation to key identifier.
 *
 * Returns: key identifier.
 **/
enum retro_key input_config_translate_str_to_rk(const char *str)
{
   size_t i;
   if (strlen(str) == 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;
   }

   RARCH_WARN("[Input]: Key name \"%s\" not found.\n", str);
   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;
}

void input_config_get_bind_string(
      void *settings_data,
      char *buf,
      const struct retro_keybind *bind,
      const struct retro_keybind *auto_bind,
      size_t size)
{
   settings_t *settings                 = (settings_t*)settings_data;
   int delim                            = 0;
   bool  input_descriptor_label_show    =
      settings->bools.input_descriptor_label_show;

   *buf                                 = '\0';

   if      (bind      && bind->joykey  != NO_BTN)
      input_config_get_bind_string_joykey(
            input_descriptor_label_show, buf, "", bind, size);
   else if (bind      && bind->joyaxis != AXIS_NONE)
      input_config_get_bind_string_joyaxis(
            input_descriptor_label_show,
            buf, "", bind, size);
   else if (auto_bind && auto_bind->joykey != NO_BTN)
      input_config_get_bind_string_joykey(
            input_descriptor_label_show, buf, "Auto: ", auto_bind, size);
   else if (auto_bind && auto_bind->joyaxis != AXIS_NONE)
      input_config_get_bind_string_joyaxis(
            input_descriptor_label_show,
            buf, "Auto: ", auto_bind, size);

   if (*buf)
      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')
      {
         char keybuf[64];

         keybuf[0] = '\0';

         if (delim)
            strlcat(buf, ", ", size);
         snprintf(keybuf, sizeof(keybuf),
               msg_hash_to_str(MENU_ENUM_LABEL_VALUE_INPUT_KEY), key);
         strlcat(buf, keybuf, size);
         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)
            strlcat(buf, ", ", size);
         strlcat(buf, msg_hash_to_str((enum msg_hash_enums)tag), size);
      }
   }

   /*completely empty?*/
   if (*buf == '\0')
      strlcat(buf, "---", size);
}

void input_config_get_bind_string_joykey(
      bool input_descriptor_label_show,
      char *buf, const char *prefix,
      const struct retro_keybind *bind, size_t size)
{
   if (GET_HAT_DIR(bind->joykey))
   {
      if (bind->joykey_label &&
            !string_is_empty(bind->joykey_label)
            && input_descriptor_label_show)
      {
         size_t len = fill_pathname_join_delim(buf, prefix,
               bind->joykey_label, ' ', size);
         buf[len  ] = ' ';
         buf[len+1] = '(';
         buf[len+2] = 'h';
         buf[len+3] = 'a';
         buf[len+4] = 't';
         buf[len+5] = ')';
         buf[len+6] = '\0';
      }
      else
      {
         const char *na_str =
            msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE);

         switch (GET_HAT_DIR(bind->joykey))
         {
            case HAT_UP_MASK:
               snprintf(buf, size, "%sHat #%u up (%s)", prefix,
                     (unsigned)GET_HAT(bind->joykey), na_str);
               break;
            case HAT_DOWN_MASK:
               snprintf(buf, size, "%sHat #%u down (%s)", prefix,
                     (unsigned)GET_HAT(bind->joykey), na_str);
               break;
            case HAT_LEFT_MASK:
               snprintf(buf, size, "%sHat #%u left (%s)", prefix,
                     (unsigned)GET_HAT(bind->joykey), na_str);
               break;
            case HAT_RIGHT_MASK:
               snprintf(buf, size, "%sHat #%u right (%s)", prefix,
                     (unsigned)GET_HAT(bind->joykey), na_str);
               break;
            default:
               snprintf(buf, size, "%sHat #%u ? (%s)", prefix,
                     (unsigned)GET_HAT(bind->joykey), na_str);
               break;
         }
      }
   }
   else
   {
      if (bind->joykey_label &&
            !string_is_empty(bind->joykey_label)
            && input_descriptor_label_show)
      {
         size_t len = fill_pathname_join_delim(buf, prefix,
               bind->joykey_label, ' ', size);
         buf[len  ] = ' ';
         buf[len+1] = '(';
         buf[len+2] = 'b';
         buf[len+3] = 't';
         buf[len+4] = 'n';
         buf[len+5] = ')';
         buf[len+6] = '\0';
      }
      else
         snprintf(buf, size, "%s%u (%s)", prefix, (unsigned)bind->joykey,
               msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE));
   }
}

void input_config_get_bind_string_joyaxis(
      bool input_descriptor_label_show,
      char *buf, const char *prefix,
      const struct retro_keybind *bind, size_t size)
{
   if (bind->joyaxis_label &&
         !string_is_empty(bind->joyaxis_label)
         && input_descriptor_label_show)
   {
      size_t len = fill_pathname_join_delim(buf, prefix,
            bind->joyaxis_label, ' ', size);
      buf[len  ] = ' ';
      buf[len+1] = '(';
      buf[len+2] = 'a';
      buf[len+3] = 'x';
      buf[len+4] = 'i';
      buf[len+5] = 's';
      buf[len+6] = ')';
      buf[len+7] = '\0';
   }
   else
   {
      const char *na_str   =
         msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE);
      if (AXIS_NEG_GET(bind->joyaxis) != AXIS_DIR_NONE)
      {
         unsigned axis = AXIS_NEG_GET(bind->joyaxis);
         snprintf(buf, size, "%s-%u (%s)", prefix, axis, na_str);
      }
      else if (AXIS_POS_GET(bind->joyaxis) != AXIS_DIR_NONE)
      {
         unsigned axis = AXIS_POS_GET(bind->joyaxis);
         snprintf(buf, size, "%s+%u (%s)", prefix, axis, na_str);
      }
   }
}

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)
{
   static char cc1[] = {"ㄱㄱㄲ ㄷㄷㄸ ㅂㅂㅃ ㅅㅅㅆ ㅈㅈㅉ"};
   static char cc2[] = {"ㅗㅏㅘ ㅗㅐㅙ ㅗㅣㅚ ㅜㅓㅝ ㅜㅔㅞ ㅜㅣㅟ ㅡㅣㅢ"};    
   static char cc3[] = {"ㄱㄱㄲ ㄱㅅㄳ ㄴㅈㄵ ㄴㅎㄶ ㄹㄱㄺ ㄹㅁㄻ ㄹㅂㄼ ㄹㅅㄽ ㄹㅌㄾ ㄹㅍㄿ ㄹㅎㅀ ㅂㅅㅄ ㅅㅅㅆ"};
   static char s1[]  = {"ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣㆍㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ"}; 
   char *tmp1;
   char *tmp2;
   int c1            = -1;
   int c2            = -1;
   int c3            =  0;
   int nv            = -1;
   char utf8[8]	   = {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 = (tmp1 - s1) / 3;
   if ((tmp1 = strstr(s1, padd)))
      nv = (tmp1 - s1) / 3;
   if (nv == -1 || nv >= 19 + 21)
      return ret;

   /* single element composition  */
   strlcpy(utf8, pcur, sizeof(utf8));
   strlcat(utf8, padd, sizeof(utf8));

   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;	
      ret = 0;
   }
   else
      if (c2 >= 0 && c3 == 0)							
      {
         if (nv < 19)
         {
            /* 3rd element attach */
            if (!(tmp1 = strstr(s1 + (19 + 21) * 3, padd)))
               return ret;
            c3 = (tmp1-s1)/3 - 19 - 21;
         }
         else
         {	
            /* 2nd element transform */
            strlcpy(utf8, s1 + (19 + c2) * 3, 4);
            utf8[3] = 0;
            strlcat(utf8, padd, sizeof(utf8));
            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 = (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 */
               strcat(utf8,padd);
               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 = (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 = (tmp1 - s1) / 3;
                  strlcpy(utf8, tmp2 - 6, 4);
                  if (!(tmp1 = strstr(s1 + (19 + 21) * 3, utf8)))
                     return ret;
                  c3 = (tmp1 - s1) / 3 - 19 - 21;
               }
               else
               {
                  if (!(tmp1 = strstr(s1, utf8)) || (tmp1 - s1) >= 19 * 3)
                     return ret;
                  tv = (tmp1-s1)/3;
                  c3 = 0;
               }
               *((unsigned*)padd) = get_kr_utf8(tv,nv-19,0);
               ret = get_kr_utf8(c1,c2,c3);
               return ret;
            }	
         }
         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 (len && composition == 0)
         word = state->buffer;  
      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)
      {
         unsigned i;

         for (i = 0; i < input_st->osk_last_codepoint_len; i++)
         {
            memmove(state->buffer + state->ptr - 1,
                  state->buffer + state->ptr,
                  state->size - state->ptr + 1);
            state->ptr--;
            state->size--;
         }

         word     = state->buffer;
      }
   }
   else if (ISPRINT(c))
   {
      /* Handle left/right here when suitable */
      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 word_len)
{
#ifdef HAVE_LANGEXTRA
   if (string_is_equal(word, "\xe2\x87\xa6")) /* backspace character */
      input_keyboard_event(true, '\x7f', '\x7f', 0, RETRO_DEVICE_KEYBOARD);
   else if (string_is_equal(word, "\xe2\x8f\x8e")) /* return character */
      input_keyboard_event(true, '\n', '\n', 0, RETRO_DEVICE_KEYBOARD);
   else
   if (string_is_equal(word, "\xe2\x87\xa7")) /* up arrow */
      *osk_idx = OSK_UPPERCASE_LATIN;
   else if (string_is_equal(word, "\xe2\x87\xa9")) /* down arrow */
      *osk_idx = OSK_LOWERCASE_LATIN;
   else if (string_is_equal(word,"\xe2\x8a\x95")) /* 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 && word_len == 3)
   {
      unsigned character = *((unsigned*)word) | 0x01000000;
      input_keyboard_line_event(&input_driver_st,  keyboard_line, character);
   }
#else
   if (string_is_equal(word, "Bksp"))
      input_keyboard_event(true, '\x7f', '\x7f', 0, RETRO_DEVICE_KEYBOARD);
   else if (string_is_equal(word, "Enter"))
      input_keyboard_event(true, '\n', '\n', 0, RETRO_DEVICE_KEYBOARD);
   else
   if (string_is_equal(word, "Upper"))
      *osk_idx = OSK_UPPERCASE_LATIN;
   else if (string_is_equal(word, "Lower"))
      *osk_idx = OSK_LOWERCASE_LATIN;
   else if (string_is_equal(word, "Next"))
      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, 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)
      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();
   bool input_sensors_enable   = settings->bools.input_sensors_enable;
   return input_driver_set_sensor(
      port, input_sensors_enable, action, rate);
}

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);
#ifdef HAVE_MFI
   if (!input_driver_st.secondary_joypad)
      input_driver_st.secondary_joypad  = input_joypad_init_driver("mfi", data);
#endif
}

/**
 * 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
 **/
float input_get_sensor_state(unsigned port, unsigned id)
{
   settings_t *settings                   = config_get_ptr();
   bool input_sensors_enable              = settings->bools.input_sensors_enable;

   return input_driver_get_sensor(port, input_sensors_enable, id);
}

/**
 * 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 suported, 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)
{
   settings_t           *settings         = config_get_ptr();
   if (input_driver_set_rumble_gain(
            gain, settings->uints.input_max_users))
      return true;
   return false;
}

uint64_t input_driver_get_capabilities(void)
{
   if (  !input_driver_st.current_driver ||
         !input_driver_st.current_driver->get_capabilities)
      return 0;
   return input_driver_st.current_driver->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);
#ifdef HAVE_MFI
   if (!input_driver_st.secondary_joypad)
      input_driver_st.secondary_joypad      = input_joypad_init_driver(
            "mfi",
            input_driver_st.current_data);
#endif
}

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
        )
      )
   {
      settings_t           *settings = config_get_ptr();
      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           = settings->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))
             / 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)
      return true;

   /* 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)
{
   if (!input_driver_st.current_driver || !input_driver_st.current_driver->grab_mouse)
      return false;
   input_driver_st.current_driver->grab_mouse(
         input_driver_st.current_data, true);
   return true;
}

bool input_driver_ungrab_mouse(void)
{
   if (!input_driver_st.current_driver || !input_driver_st.current_driver->grab_mouse)
      return false;
   input_driver_st.current_driver->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;

   retro_assert(sizeof(input_config_binds[0]) >= sizeof(retro_keybinds_1));
   retro_assert(sizeof(input_config_binds[1]) >= sizeof(retro_keybinds_rest));

   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_path[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;
   unsigned 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 (
               string_is_empty(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 (string_is_empty(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 (string_is_empty(input_st->input_device_info[port].name))
      return NULL;
   return input_st->input_device_info[port].name;
}


const char *input_config_get_device_config_path(unsigned port)
{
   input_driver_state_t *input_st = &input_driver_st;
   if (string_is_empty(input_st->input_device_info[port].config_path))
      return NULL;
   return input_st->input_device_info[port].config_path;
}

const char *input_config_get_device_display_name(unsigned port)
{
   input_driver_state_t *input_st = &input_driver_st;
   if (string_is_empty(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 (string_is_empty(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 (string_is_empty(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 (string_is_empty(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 (!string_is_empty(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_path(unsigned port, const char *path)
{
   if (!string_is_empty(path))
   {
      char parent_dir_name[128];
      input_driver_state_t *input_st = &input_driver_st;
      if (fill_pathname_parent_dir_name(parent_dir_name,
               path, sizeof(parent_dir_name)))
         fill_pathname_join_special(
               input_st->input_device_info[port].config_path,
               parent_dir_name, path_basename_nocompression(path),
               sizeof(input_st->input_device_info[port].config_path));
   }
}

void input_config_set_device_config_name(unsigned port, const char *name)
{
   input_driver_state_t *input_st = &input_driver_st;
   if (!string_is_empty(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 (!string_is_empty(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_path(unsigned port)
{
   input_driver_state_t *input_st = &input_driver_st;
   input_st->input_device_info[port].config_path[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 (string_is_empty(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 (!string_is_empty(name))
      string_copy_only_ascii(name_ascii, name);

   if (!string_is_empty(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;
   input_driver_state_t *input_st = &input_driver_st;
   config_file_t            *conf = (config_file_t*)data;

   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];
         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 *prefix         = NULL;
         const char *btn            = NULL;
         struct config_entry_list
            *entry                  = NULL;


         if (!bind || !bind->valid || !keybind)
            continue;
         if (!keybind->valid)
            continue;
         meta                       = keybind->meta;
         btn                        = keybind->base;
         prefix                     = input_config_get_prefix(i, meta);
         if (!btn || !prefix)
            continue;

         fill_pathname_join_delim(str, prefix, btn,  '_', sizeof(str));

         /* Clear old mapping bit */
         BIT512_CLEAR_PTR(&input_st->keyboard_mapping_bits, bind->key);

         entry                      = config_get_entry(conf, str);
         if (entry && !string_is_empty(entry->value))
            bind->key               = input_config_translate_str_to_rk(
                  entry->value);
         /* Store mapping bit */
         BIT512_SET_PTR(&input_st->keyboard_mapping_bits, bind->key);

         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)
{
   bool input_network_cmd_enable     = settings->bools.network_cmd_enable;
   unsigned network_cmd_port         = settings->uints.network_cmd_port;
#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;
      bool grab_stdin                =
         input_st->current_driver->grab_stdin &&
         input_st->current_driver->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
   if (input_network_cmd_enable)
   {
      if (!(input_st->command[1] = command_network_new(network_cmd_port)))
         RARCH_ERR("Failed to initialize the network command interface.\n");
   }
#endif

#ifdef HAVE_LAKKA
   if (!(input_st->command[2] = command_uds_new()))
      RARCH_ERR("Failed to initialize the UDS command interface.\n");
#endif
}

void input_driver_deinit_command(input_driver_state_t *input_st)
{
   int i;
   for (i = 0; i < 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
void input_overlay_deinit(void)
{
   input_overlay_free(input_driver_st.overlay_ptr);
   input_driver_st.overlay_ptr = NULL;
}

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

      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 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;
}

/* task_data = overlay_task_data_t* */
static void input_overlay_loaded(retro_task_t *task,
      void *task_data, void *user_data, const char *err)
{
   size_t i;
   overlay_task_data_t              *data = (overlay_task_data_t*)task_data;
   input_overlay_t                    *ol = NULL;
   const video_overlay_interface_t *iface = NULL;
   settings_t *settings                   = config_get_ptr();
   bool input_overlay_show_mouse_cursor   = settings->bools.input_overlay_show_mouse_cursor;
   bool inp_overlay_auto_rotate           = settings->bools.input_overlay_auto_rotate;
   bool input_overlay_enable              = settings->bools.input_overlay_enable;
   video_driver_state_t *video_st         = video_state_get_ptr();
   input_driver_state_t *input_st         = &input_driver_st;
   if (err)
      return;

   if (data->flags & OVERLAY_LOADER_ENABLE)
   {
#ifdef HAVE_MENU
      struct menu_state *menu_st = menu_state_get_ptr();
      bool refresh               = false;

      /* Update menu entries */
      if (menu_st->overlay_types != data->overlay_types)
      {
         menu_st->overlay_types = data->overlay_types;
         menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
      }

      /* We can't display when the menu is up */
      if (  (data->flags & OVERLAY_LOADER_HIDE_IN_MENU) &&
            (menu_st->flags & MENU_ST_FLAG_ALIVE))
         goto abort_load;
#endif

      /* If 'hide_when_gamepad_connected' is enabled,
       * we can't display when a gamepad is connected */
      if (  (data->flags & OVERLAY_LOADER_HIDE_WHEN_GAMEPAD_CONNECTED) &&
            (input_config_get_device_name(0) != NULL))
         goto abort_load;
   }

   if (  !(data->flags & OVERLAY_LOADER_ENABLE)  ||
         !video_driver_overlay_interface(&iface) ||
         !iface)
   {
      RARCH_ERR("Overlay interface is not present in video driver,"
            " or not enabled.\n");
      goto abort_load;
   }

   ol             = (input_overlay_t*)calloc(1, sizeof(*ol));
   ol->overlays   = data->overlays;
   ol->size       = data->size;
   ol->active     = data->active;
   ol->iface      = iface;
   ol->iface_data = video_st->data;

   input_overlay_load_active(input_st->overlay_visibility,
         ol, data->overlay_opacity);

   /* Enable or disable the overlay. */
   if (data->flags & OVERLAY_LOADER_ENABLE)
      ol->flags |= INPUT_OVERLAY_ENABLE;
   else
      ol->flags &= ~INPUT_OVERLAY_ENABLE;

   if (ol->iface->enable)
      ol->iface->enable(ol->iface_data, (ol->flags & INPUT_OVERLAY_ENABLE));

   input_overlay_set_scale_factor(ol, &data->layout_desc,
         video_st->width, video_st->height);

   ol->next_index = (unsigned)((ol->index + 1) % ol->size);
   ol->state      = OVERLAY_STATUS_NONE;
   ol->flags     |= INPUT_OVERLAY_ALIVE;

   /* 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_overlays(input_st->overlay_ptr);
      free(input_st->overlay_ptr);
   }
   input_st->overlay_ptr = ol;

   free(data);

   if (!input_overlay_show_mouse_cursor)
      video_driver_hide_mouse();

   /* Attempt to automatically rotate overlay, if required */
   if (inp_overlay_auto_rotate)
      input_overlay_auto_rotate_(
            video_st->width,
            video_st->height,
            input_overlay_enable,
            input_st->overlay_ptr);

   input_overlay_set_eightway_diagonal_sensitivity();

   return;

abort_load:
   for (i = 0; i < data->size; i++)
      input_overlay_free_overlay(&data->overlays[i]);

   free(data->overlays);
   free(data);
}

void input_overlay_init(void)
{
   settings_t *settings                     = config_get_ptr();
   bool input_overlay_enable                = settings->bools.input_overlay_enable;
   bool input_overlay_auto_scale            = settings->bools.input_overlay_auto_scale;
   const char *path_overlay                 = settings->paths.path_overlay;
   float overlay_opacity                    = settings->floats.input_overlay_opacity;
   float overlay_scale_landscape            = settings->floats.input_overlay_scale_landscape;
   float overlay_aspect_adjust_landscape    = settings->floats.input_overlay_aspect_adjust_landscape;
   float overlay_x_separation_landscape     = settings->floats.input_overlay_x_separation_landscape;
   float overlay_y_separation_landscape     = settings->floats.input_overlay_y_separation_landscape;
   float overlay_x_offset_landscape         = settings->floats.input_overlay_x_offset_landscape;
   float overlay_y_offset_landscape         = settings->floats.input_overlay_y_offset_landscape;
   float overlay_scale_portrait             = settings->floats.input_overlay_scale_portrait;
   float overlay_aspect_adjust_portrait     = settings->floats.input_overlay_aspect_adjust_portrait;
   float overlay_x_separation_portrait      = settings->floats.input_overlay_x_separation_portrait;
   float overlay_y_separation_portrait      = settings->floats.input_overlay_y_separation_portrait;
   float overlay_x_offset_portrait          = settings->floats.input_overlay_x_offset_portrait;
   float overlay_y_offset_portrait          = settings->floats.input_overlay_y_offset_portrait;
   float overlay_touch_scale                = (float)settings->uints.input_touch_scale;

   bool load_enabled                        = input_overlay_enable;
#ifdef HAVE_MENU
   bool overlay_hide_in_menu                = settings->bools.input_overlay_hide_in_menu;
#else
   bool overlay_hide_in_menu                = false;
#endif
   bool overlay_hide_when_gamepad_connected = settings->bools.input_overlay_hide_when_gamepad_connected;
#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

   input_overlay_deinit();

   /* Cancel load if 'hide_when_gamepad_connected' is
    * enabled and a gamepad is currently connected */
   if (overlay_hide_when_gamepad_connected)
      load_enabled = load_enabled && (input_config_get_device_name(0) == NULL);

   if (load_enabled)
   {
      overlay_layout_desc_t layout_desc;

      layout_desc.scale_landscape         = overlay_scale_landscape;
      layout_desc.aspect_adjust_landscape = overlay_aspect_adjust_landscape;
      layout_desc.x_separation_landscape  = overlay_x_separation_landscape;
      layout_desc.y_separation_landscape  = overlay_y_separation_landscape;
      layout_desc.x_offset_landscape      = overlay_x_offset_landscape;
      layout_desc.y_offset_landscape      = overlay_y_offset_landscape;
      layout_desc.scale_portrait          = overlay_scale_portrait;
      layout_desc.aspect_adjust_portrait  = overlay_aspect_adjust_portrait;
      layout_desc.x_separation_portrait   = overlay_x_separation_portrait;
      layout_desc.y_separation_portrait   = overlay_y_separation_portrait;
      layout_desc.x_offset_portrait       = overlay_x_offset_portrait;
      layout_desc.y_offset_portrait       = overlay_y_offset_portrait;
      layout_desc.touch_scale             = overlay_touch_scale;
      layout_desc.auto_scale              = input_overlay_auto_scale;

      task_push_overlay_load_default(input_overlay_loaded,
            path_overlay,
            overlay_hide_in_menu,
            overlay_hide_when_gamepad_connected,
            input_overlay_enable,
            overlay_opacity,
            &layout_desc,
            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, 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 < 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;
}

/**
 * input_keys_pressed:
 *
 * Grab an input sample for this frame.
 */
static void input_keys_pressed(
      unsigned 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)
{
   unsigned i;
   input_driver_state_t *input_st = &input_driver_st;

   if (!binds)
      return;

   if (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[port],
               input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED,
               port, RETRO_DEVICE_JOYPAD, 0,
               RARCH_ENABLE_HOTKEY))
      {
         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->input_hotkey_block_counter = 0;
         input_st->flags |= INP_FLAG_BLOCK_HOTKEY;
      }
   }
   if (!is_menu && binds[port][RARCH_GAME_FOCUS_TOGGLE].valid)
   {
      const struct retro_keybind *focus_binds_auto =
         &input_autoconf_binds[port][RARCH_GAME_FOCUS_TOGGLE];
      const struct retro_keybind *focus_normal     =
         &binds[port][RARCH_GAME_FOCUS_TOGGLE];

      /* Allows rarch_focus_toggle hotkey to still work
       * even though every hotkey is blocked */
      if (CHECK_INPUT_DRIVER_BLOCK_HOTKEY(
               focus_normal, focus_binds_auto))
      {
         if (input_state_wrap(
                  input_st->current_driver,
                  input_st->current_data,
                  input_st->primary_joypad,
                  sec_joypad,
                  joypad_info,
                  &binds[port],
                  input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED,
                  port,
                  RETRO_DEVICE_JOYPAD, 0, RARCH_GAME_FOCUS_TOGGLE))
            input_st->flags &= ~INP_FLAG_BLOCK_HOTKEY;
      }
   }

   {
      int16_t ret = 0;

      /* Check the libretro input first */
      if (!(input_st->flags & INP_FLAG_BLOCK_LIBRETRO_INPUT))
         ret = input_state_wrap(
               input_st->current_driver,
               input_st->current_data,
               input_st->primary_joypad,
               sec_joypad,
               joypad_info, &binds[port],
               input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED,
               port, RETRO_DEVICE_JOYPAD, 0,
               RETRO_DEVICE_ID_JOYPAD_MASK);

      for (i = 0; i < RARCH_FIRST_META_KEY; i++)
      {
         if (
                  (ret & (UINT64_C(1) <<  i))
               || input_keys_pressed_other_sources(input_st,
                  i, p_new_state))
         {
            BIT256_SET_PTR(p_new_state, i);
         }
      }
   }

   /* Check the hotkeys */
   if (input_st->flags & INP_FLAG_BLOCK_HOTKEY)
   {
      for (i = RARCH_FIRST_META_KEY; i < RARCH_BIND_LIST_END; i++)
      {
         if (
                  BIT64_GET(lifecycle_state, i)
               || input_keys_pressed_other_sources(input_st,
                  i, p_new_state))
         {
            BIT256_SET_PTR(p_new_state, i);
         }
      }
   }
   else
   {
      for (i = RARCH_FIRST_META_KEY; i < RARCH_BIND_LIST_END; i++)
      {
         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[port],
                  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))
         {
            BIT256_SET_PTR(p_new_state, i);
         }
      }
   }
}

static int16_t input_state_device(
      input_driver_state_t *input_st,
      settings_t *settings,
      input_mapper_t *handle,
      unsigned input_analog_dpad_mode,
      int16_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;
#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
            }

            /* Don't allow turbo for D-pad. */
            if (     (id  < RETRO_DEVICE_ID_JOYPAD_UP)    ||
                  (    (id  > RETRO_DEVICE_ID_JOYPAD_RIGHT) &&
                       (id <= RETRO_DEVICE_ID_JOYPAD_R3)))
            {
               /*
                * Apply turbo button if activated.
                */
               unsigned turbo_mode = settings->uints.input_turbo_mode;

               if (turbo_mode > INPUT_TURBO_MODE_CLASSIC)
               {
                  /* Pressing turbo button toggles turbo mode 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 buttons. */
                     if (input_st->turbo_btns.enable[port]
                           != (1 << settings->uints.input_turbo_default_button))
                     {
                        static const int button_map[]={
                           RETRO_DEVICE_ID_JOYPAD_B,
                           RETRO_DEVICE_ID_JOYPAD_Y,
                           RETRO_DEVICE_ID_JOYPAD_A,
                           RETRO_DEVICE_ID_JOYPAD_X,
                           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};
                        input_st->turbo_btns.enable[port] = 1 << button_map[
                           MIN(
                                 ARRAY_SIZE(button_map) - 1,
                                 settings->uints.input_turbo_default_button)];
                     }
                     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;
                        /* Toggle turbo for pressed button but make
                         * sure at least one button has turbo */
                        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))
                  {
                     /* If turbo button is enabled for this key ID */
                     res = ((   input_st->turbo_btns.count
                              % settings->uints.input_turbo_period)
                           < settings->uints.input_turbo_duty_cycle);
                  }
               }
               else
               {
                  /* If turbo button is held, all buttons pressed except
                   * for D-pad 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
                                 % settings->uints.input_turbo_period)
                              < settings->uints.input_turbo_duty_cycle);
                  }
                  else
                     input_st->turbo_btns.enable[port] &= ~(1 << id);
               }
            }
         }

         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
                  && 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 (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_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:

         if (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;
}

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;
   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
#ifdef HAVE_OVERLAY
   float input_overlay_opacity    = settings->floats.input_overlay_opacity;
#endif
   bool input_remap_binds_enable  = settings->bools.input_remap_binds_enable;
   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_st->current_driver
         && input_st->current_driver->poll)
      input_st->current_driver->poll(input_st->current_data);

   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;
      return;
   }

   /* This rarch_joypad_info_t struct contains the device index + autoconfig binds for the
    * controller to be queried, and also (for unknown reasons) the analog axis threshold
    * when mapping analog stick to dpad input. */
   for (i = 0; i < max_users; i++)
   {
      joypad_info[i].axis_threshold           = settings->floats.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];

      input_st->turbo_btns.frame_enable[i]    = (*input_st->libretro_input_binds[i])[RARCH_TURBO_ENABLE].valid ?
         input_state_wrap(
               input_st->current_driver,
               input_st->current_data,
               joypad,
               sec_joypad,
               &joypad_info[i],
               (*input_st->libretro_input_binds),
               input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED,
               (unsigned)i,
               RETRO_DEVICE_JOYPAD,
               0,
               RARCH_TURBO_ENABLE) : 0;
   }

#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];

      switch (input_analog_dpad_mode)
      {
         case ANALOG_DPAD_LSTICK:
         case ANALOG_DPAD_RSTICK:
            {
               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;
         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

#ifdef HAVE_MENU
   if (!(menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE))
#endif
   if (input_remap_binds_enable)
   {
#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;

      for (i = 0; i < max_users; i++)
      {
         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:
               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;
            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, j;
                  int16_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),
                        input_st->flags & INP_FLAG_KB_MAPPING_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);
                     }
                  }

                  /* This is the analog joypad index -
                   * handles only the two analog axes */
                  for (k = 0; k < 2; k++)
                  {
                     /* This is the analog joypad ident */
                     for (j = 0; j < 2; j++)
                     {
                        unsigned offset = 0 + (k * 4) + (j * 2);
                        int16_t     val = input_joypad_analog_axis(
                              input_analog_dpad_mode,
                              input_analog_deadzone,
                              input_analog_sensitivity,
                              joypad,
                              &joypad_info[i],
                              k,
                              j,
                              (*input_st->libretro_input_binds[i]));

                        if (val >= 0)
                           p_new_state->analogs[offset]   = val;
                        else
                           p_new_state->analogs[offset+1] = val;
                     }
                  }
               }
               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
                     {
                        int invert = 1;

                        if (remap_button % 2 != 0)
                           invert = -1;

                        handle->analog_value[i][
                           remap_button - RARCH_FIRST_CUSTOM_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;

                        if (remap_axis_bind < sizeof(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;
         }
      }
   }

#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 (input_st->remote->net_fd[user] < 0)
               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
#define MAGIC_INDEX        0
#define SERIALIZER_INDEX   1
#define CRC_INDEX          2
#define STATE_SIZE_INDEX   3

#define BSV_MAGIC          0x42535631

static bool bsv_movie_init_playback(
      bsv_movie_t *handle, const char *path)
{
   uint32_t state_size       = 0;
   uint32_t header[4]        = {0};
   intfstream_t *file        = intfstream_open_file(path,
         RETRO_VFS_FILE_ACCESS_READ,
         RETRO_VFS_FILE_ACCESS_HINT_NONE);

   if (!file)
   {
      RARCH_ERR("Could not open BSV file for playback, path : \"%s\".\n", path);
      return false;
   }

   handle->file              = file;
   handle->playback          = true;

   intfstream_read(handle->file, header, sizeof(uint32_t) * 4);
   /* Compatibility with old implementation that
    * used incorrect documentation. */
   if (swap_if_little32(header[MAGIC_INDEX]) != BSV_MAGIC
         && swap_if_big32(header[MAGIC_INDEX]) != BSV_MAGIC)
   {
      RARCH_ERR("%s\n", msg_hash_to_str(MSG_MOVIE_FILE_IS_NOT_A_VALID_BSV1_FILE));
      return false;
   }

   state_size = swap_if_big32(header[STATE_SIZE_INDEX]);

#if 0
   RARCH_ERR("----- debug %u -----\n", header[0]);
   RARCH_ERR("----- debug %u -----\n", header[1]);
   RARCH_ERR("----- debug %u -----\n", header[2]);
   RARCH_ERR("----- debug %u -----\n", header[3]);
#endif

   if (state_size)
   {
      retro_ctx_size_info_t info;
      retro_ctx_serialize_info_t serial_info;
      uint8_t *buf       = (uint8_t*)malloc(state_size);

      if (!buf)
         return false;

      handle->state      = buf;
      handle->state_size = state_size;
      if (intfstream_read(handle->file,
               handle->state, state_size) != state_size)
      {
         RARCH_ERR("%s\n", msg_hash_to_str(MSG_COULD_NOT_READ_STATE_FROM_MOVIE));
         return false;
      }

      core_serialize_size( &info);

      if (info.size == state_size)
      {
         serial_info.data_const = handle->state;
         serial_info.size       = state_size;
         core_unserialize(&serial_info);
      }
      else
         RARCH_WARN("%s\n",
               msg_hash_to_str(MSG_MOVIE_FORMAT_DIFFERENT_SERIALIZER_VERSION));
   }

   handle->min_file_pos = sizeof(header) + state_size;

   return true;
}

static bool bsv_movie_init_record(
      bsv_movie_t *handle, const char *path)
{
   retro_ctx_size_info_t info;
   uint32_t state_size       = 0;
   uint32_t content_crc      = 0;
   uint32_t header[4]        = {0};
   intfstream_t *file        = intfstream_open_file(path,
         RETRO_VFS_FILE_ACCESS_WRITE,
         RETRO_VFS_FILE_ACCESS_HINT_NONE);

   if (!file)
   {
      RARCH_ERR("Could not open BSV file for recording, path : \"%s\".\n", path);
      return false;
   }

   handle->file             = file;

   content_crc              = content_get_crc();

   /* This value is supposed to show up as
    * BSV1 in a HEX editor, big-endian. */
   header[MAGIC_INDEX]      = swap_if_little32(BSV_MAGIC);
   header[CRC_INDEX]        = swap_if_big32(content_crc);

   core_serialize_size(&info);

   state_size               = (unsigned)info.size;

   header[STATE_SIZE_INDEX] = swap_if_big32(state_size);

   intfstream_write(handle->file, header, 4 * sizeof(uint32_t));

   handle->min_file_pos     = sizeof(header) + state_size;
   handle->state_size       = state_size;

   if (state_size)
   {
      retro_ctx_serialize_info_t serial_info;
      uint8_t *st      = (uint8_t*)malloc(state_size);

      if (!st)
         return false;

      handle->state    = st;

      serial_info.data = handle->state;
      serial_info.size = state_size;

      core_serialize(&serial_info);

      intfstream_write(handle->file,
            handle->state, state_size);
   }

   return true;
}

static void bsv_movie_free(bsv_movie_t *handle)
{
   intfstream_close(handle->file);
   free(handle->file);

   free(handle->state);
   free(handle->frame_pos);
   free(handle);
}

static bsv_movie_t *bsv_movie_init_internal(const char *path,
      enum rarch_movie_type type)
{
   size_t *frame_pos   = NULL;
   bsv_movie_t *handle = (bsv_movie_t*)calloc(1, sizeof(*handle));

   if (!handle)
      return NULL;

   if (type == RARCH_MOVIE_PLAYBACK)
   {
      if (!bsv_movie_init_playback(handle, path))
         goto error;
   }
   else if (!bsv_movie_init_record(handle, path))
      goto error;

   /* Just pick something really large
    * ~1 million frames rewind should do the trick. */
   if (!(frame_pos = (size_t*)calloc((1 << 20), sizeof(size_t))))
      goto error;

   handle->frame_pos       = frame_pos;

   handle->frame_pos[0]    = handle->min_file_pos;
   handle->frame_mask      = (1 << 20) - 1;

   return handle;

error:
   if (handle)
      bsv_movie_free(handle);
   return NULL;
}

static bool runloop_check_movie_init(input_driver_state_t *input_st,
      settings_t *settings)
{
   size_t _len;
   char msg[16384], path[8192];
   bsv_movie_t *state          = NULL;
   int state_slot              = settings->ints.state_slot;
   msg[0]                      =  '\0';

   configuration_set_uint(settings, settings->uints.rewind_granularity, 1);

   _len = strlcpy(path,
         input_st->bsv_movie_state.movie_path, sizeof(path));
   if (state_slot > 0)
      snprintf(path + _len, sizeof(path) - _len, "%d", state_slot);
   strlcat(path, ".bsv", sizeof(path));

   snprintf(msg, sizeof(msg), "%s \"%s\".",
         msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO),
         path);

   if (!(state = bsv_movie_init_internal(path, RARCH_MOVIE_RECORD)))
   {
      runloop_msg_queue_push(
            msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD),
            2, 180, true,
            NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
      RARCH_ERR("%s\n",
            msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD));
      return false;
   }

   input_st->bsv_movie_state_handle         = state;

   runloop_msg_queue_push(msg, 2, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
   RARCH_LOG("%s \"%s\".\n",
         msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO),
         path);

   return true;
}

void bsv_movie_frame_rewind(void)
{
   input_driver_state_t *input_st = &input_driver_st;
   bsv_movie_t         *handle    = input_st->bsv_movie_state_handle;

   if (!handle)
      return;

   handle->did_rewind = true;

   if (     (handle->frame_ptr <= 1)
         && (handle->frame_pos[0] == handle->min_file_pos))
   {
      /* If we're at the beginning... */
      handle->frame_ptr = 0;
      intfstream_seek(handle->file, (int)handle->min_file_pos, SEEK_SET);
   }
   else
   {
      /* First time rewind is performed, the old frame is simply replayed.
       * However, playing back that frame caused us to read data, and push
       * data to the ring buffer.
       *
       * Sucessively rewinding frames, we need to rewind past the read data,
       * plus another. */
      handle->frame_ptr = (handle->frame_ptr -
            (handle->first_rewind ? 1 : 2)) & handle->frame_mask;
      intfstream_seek(handle->file,
            (int)handle->frame_pos[handle->frame_ptr], SEEK_SET);
   }

   if (intfstream_tell(handle->file) <= (long)handle->min_file_pos)
   {
      /* We rewound past the beginning. */

      if (!handle->playback)
      {
         retro_ctx_serialize_info_t serial_info;

         /* If recording, we simply reset
          * the starting point. Nice and easy. */

         intfstream_seek(handle->file, 4 * sizeof(uint32_t), SEEK_SET);

         serial_info.data = handle->state;
         serial_info.size = handle->state_size;

         core_serialize(&serial_info);

         intfstream_write(handle->file, handle->state, handle->state_size);
      }
      else
         intfstream_seek(handle->file, (int)handle->min_file_pos, SEEK_SET);
   }
}

bool bsv_movie_init(input_driver_state_t *input_st)
{
   bsv_movie_t *state = NULL;
   if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK)
   {
      const char *starting_movie_str = NULL;
      if (!(state = bsv_movie_init_internal(
               input_st->bsv_movie_state.movie_start_path,
               RARCH_MOVIE_PLAYBACK)))
      {
         RARCH_ERR("%s: \"%s\".\n",
               msg_hash_to_str(MSG_FAILED_TO_LOAD_MOVIE_FILE),
               input_st->bsv_movie_state.movie_start_path);
         return false;
      }

      input_st->bsv_movie_state_handle = state;
      input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_PLAYBACK;
      starting_movie_str                       =
         msg_hash_to_str(MSG_STARTING_MOVIE_PLAYBACK);

      runloop_msg_queue_push(starting_movie_str,
            2, 180, false,
            NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
      RARCH_LOG("%s.\n", starting_movie_str);

      return true;
   }
   else if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_RECORDING)
   {
      char msg[8192];
      const char *movie_rec_str = msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO);

      if (!(state = bsv_movie_init_internal(
               input_st->bsv_movie_state.movie_start_path,
               RARCH_MOVIE_RECORD)))
      {
         const char *movie_rec_fail_str =
            msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD);
         runloop_msg_queue_push(movie_rec_fail_str,
               1, 180, true,
               NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
         RARCH_ERR("%s.\n", movie_rec_fail_str);
         return false;
      }

      input_st->bsv_movie_state_handle         = state;
      snprintf(msg, sizeof(msg),
            "%s \"%s\".", movie_rec_str,
            input_st->bsv_movie_state.movie_start_path);

      runloop_msg_queue_push(msg, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
      RARCH_LOG("%s \"%s\".\n", movie_rec_str,
            input_st->bsv_movie_state.movie_start_path);

      return true;
   }

   return false;
}

void bsv_movie_deinit(input_driver_state_t *input_st)
{
   if (input_st->bsv_movie_state_handle)
      bsv_movie_free(input_st->bsv_movie_state_handle);
   input_st->bsv_movie_state_handle = NULL;
}

bool bsv_movie_check(input_driver_state_t *input_st,
      settings_t *settings)
{
   const char *movie_rec_stopped_str = NULL;
   if (!input_st->bsv_movie_state_handle)
      return runloop_check_movie_init(input_st, settings);

   if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK)
   {
      const char *movie_playback_end_str = NULL;
      /* Checks if movie is being played back. */
      if (!(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_END))
         return false;
      movie_playback_end_str = msg_hash_to_str(MSG_MOVIE_PLAYBACK_ENDED);
      runloop_msg_queue_push(
            movie_playback_end_str, 2, 180, false,
            NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
      RARCH_LOG("%s\n", movie_playback_end_str);

      bsv_movie_deinit(input_st);

      input_st->bsv_movie_state.flags &= ~(
              BSV_FLAG_MOVIE_END
            | BSV_FLAG_MOVIE_PLAYBACK);
      return true;
   }

   /* Checks if movie is being recorded. */
   if (!input_st->bsv_movie_state_handle)
      return false;

   movie_rec_stopped_str = msg_hash_to_str(MSG_MOVIE_RECORD_STOPPED);
   runloop_msg_queue_push(movie_rec_stopped_str,
         2, 180, true,
         NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
   RARCH_LOG("%s\n", movie_rec_stopped_str);

   bsv_movie_deinit(input_st);

   return true;
}
#endif

int16_t input_state_internal(unsigned port, unsigned device,
      unsigned idx, unsigned id)
{
   rarch_joypad_info_t joypad_info;
   unsigned mapped_port;
   input_driver_state_t *input_st          = &input_driver_st;
   settings_t *settings                    = config_get_ptr();
   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];
   bool input_driver_analog_requested      = input_st->analog_requested[port];
   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
#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);
#endif
   bool bitmask_enabled                    = false;
   unsigned max_users                      = settings->uints.input_max_users;
   int16_t result                          = 0;

   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;

   /* 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;
      unsigned 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:
            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;
         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),
            input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED,
            mapped_port, device, idx, id);

      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)
         {
            unsigned 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);
      }

      /* 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;
   }

#ifdef HAVE_BSV_MOVIE
   /* Save input to BSV record, if enabled */
   if (BSV_MOVIE_IS_PLAYBACK_OFF())
   {
      result = swap_if_big16(result);
      intfstream_write(
            input_st->bsv_movie_state_handle->file, &result, 2);
   }
#endif

   return result;
}

int16_t input_driver_state_wrapper(unsigned port, unsigned device,
      unsigned idx, unsigned id)
{
   input_driver_state_t
      *input_st                = &input_driver_st;
   int16_t result              = 0;
#ifdef HAVE_BSV_MOVIE
   /* Load input from BSV record, if enabled */
   if (BSV_MOVIE_IS_PLAYBACK_ON())
   {
      int16_t bsv_result = 0;
      if (intfstream_read(
               input_st->bsv_movie_state_handle->file,
               &bsv_result, 2) == 2)
      {
#ifdef HAVE_CHEEVOS
         rcheevos_pause_hardcore();
#endif
         return swap_if_big16(bsv_result);
      }

      input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
   }
#endif

   /* Read input state */
   result = input_state_internal(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
   /* Save input to BSV record, if enabled */
   if (BSV_MOVIE_IS_PLAYBACK_OFF())
   {
      result = swap_if_big16(result);
      intfstream_write(input_st->bsv_movie_state_handle->file, &result, 2);
   }
#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->old_analog_dpad_mode[i] = settings->uints.input_analog_dpad_mode[i];
      input_st->old_libretro_device[i]  = device;
   }

   input_st->flags |= INP_FLAG_OLD_ANALOG_DPAD_MODE_SET
                    | INP_FLAG_OLD_LIBRETRO_DEVICE_SET;
}

void input_remapping_restore_global_config(bool clear_cache)
{
   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 ((input_st->flags & INP_FLAG_OLD_ANALOG_DPAD_MODE_SET)
          && (settings->uints.input_analog_dpad_mode[i] !=
               input_st->old_analog_dpad_mode[i]))
         configuration_set_uint(settings,
               settings->uints.input_analog_dpad_mode[i],
               input_st->old_analog_dpad_mode[i]);

      if (   (input_st->flags & INP_FLAG_OLD_LIBRETRO_DEVICE_SET)
          && (settings->uints.input_libretro_device[i] !=
               input_st->old_libretro_device[i]))
         configuration_set_uint(settings,
               settings->uints.input_libretro_device[i],
               input_st->old_libretro_device[i]);
   }

end:
   if (clear_cache)
      input_st->flags &= ~(INP_FLAG_OLD_ANALOG_DPAD_MODE_SET
                         | INP_FLAG_OLD_LIBRETRO_DEVICE_SET
                         | 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_FIRST_CUSTOM_BIND + 8); 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);
}

void input_driver_collect_system_input(input_driver_state_t *input_st,
      settings_t *settings, input_bits_t *current_bits)
{
   int port;
   rarch_joypad_info_t joypad_info;
   unsigned block_delay                = settings->uints.input_hotkey_block_delay;
   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
#ifdef HAVE_MENU
   bool display_kb                     = menu_input_dialog_get_display_kb();
   bool menu_is_alive                  = menu_state_get_ptr()->flags &
      MENU_ST_FLAG_ALIVE;
   bool menu_input_active              = menu_is_alive &&
         !(settings->bools.menu_unified_controls && !display_kb);
#endif
   input_driver_t *current_input       = input_st->current_driver;
   unsigned max_users                  = settings->uints.input_max_users;
   bool all_users_control_menu         = settings->bools.input_all_users_control_menu;
   joypad_info.axis_threshold          = settings->floats.input_axis_threshold;

   /* Gather input from each (enabled) joypad */
   for (port = 0; port < (int)max_users; port++)
   {
      input_bits_t tmp_bits;
      input_bits_t *loop_bits                = NULL;
      const struct retro_keybind *binds_norm = &input_config_binds[port][RARCH_ENABLE_HOTKEY];
      const struct retro_keybind *binds_auto = &input_autoconf_binds[port][RARCH_ENABLE_HOTKEY];
      struct retro_keybind *auto_binds       = input_autoconf_binds[port];
      struct retro_keybind *general_binds    = input_config_binds[port];
      joypad_info.joy_idx                    = settings->uints.input_joypad_index[port];
      joypad_info.auto_binds                 = input_autoconf_binds[joypad_info.joy_idx];

      if (port == 0)
         loop_bits = current_bits;
      else
      {
         loop_bits = &tmp_bits;
         BIT256_CLEAR_ALL_PTR(loop_bits);
      }

#ifdef HAVE_MENU
      if (menu_is_alive)
      {
         int k;
         int s;

         /* Push analog to D-Pad mappings to binds. */
         for (k = RETRO_DEVICE_ID_JOYPAD_UP; k <= RETRO_DEVICE_ID_JOYPAD_RIGHT; k++)
         {
            (auto_binds)[k].orig_joyaxis    = (auto_binds)[k].joyaxis;
            (general_binds)[k].orig_joyaxis = (general_binds)[k].joyaxis;
         }

         /* Read input from both analog sticks. */
         for (s = RETRO_DEVICE_INDEX_ANALOG_LEFT; s <= RETRO_DEVICE_INDEX_ANALOG_RIGHT; s++)
         {
            unsigned x_plus  = RARCH_ANALOG_LEFT_X_PLUS;
            unsigned y_plus  = RARCH_ANALOG_LEFT_Y_PLUS;
            unsigned x_minus = RARCH_ANALOG_LEFT_X_MINUS;
            unsigned y_minus = RARCH_ANALOG_LEFT_Y_MINUS;

            if (s == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
            {
               x_plus  = RARCH_ANALOG_RIGHT_X_PLUS;
               y_plus  = RARCH_ANALOG_RIGHT_Y_PLUS;
               x_minus = RARCH_ANALOG_RIGHT_X_MINUS;
               y_minus = RARCH_ANALOG_RIGHT_Y_MINUS;
            }

            if (!INHERIT_JOYAXIS(auto_binds))
            {
               unsigned j = x_plus + 3;
               /* Inherit joyaxis from analogs. */
               for (k = RETRO_DEVICE_ID_JOYPAD_UP; k <= RETRO_DEVICE_ID_JOYPAD_RIGHT; k++)
               {
                  if ((auto_binds)[j].joyaxis != AXIS_NONE &&
                        ((float)abs(joypad->axis(port, (uint32_t)(auto_binds)[j].joyaxis))
                         / 0x8000) > joypad_info.axis_threshold)
                     (auto_binds)[k].joyaxis = (auto_binds)[j].joyaxis;
                  j--;
               }
            }

            if (!INHERIT_JOYAXIS(general_binds))
            {
               unsigned j = x_plus + 3;
               /* Inherit joyaxis from analogs. */
               for (k = RETRO_DEVICE_ID_JOYPAD_UP; k <= RETRO_DEVICE_ID_JOYPAD_RIGHT; k++)
               {
                  if ((general_binds)[j].joyaxis != AXIS_NONE &&
                        ((float)abs(joypad->axis(port, (uint32_t)(general_binds)[j].joyaxis))
                         / 0x8000) > joypad_info.axis_threshold)
                     (general_binds)[k].joyaxis = (general_binds)[j].joyaxis;
                  j--;
               }
            }
         }
      }
#endif /* HAVE_MENU */

      input_keys_pressed(port,
#ifdef HAVE_MENU
            menu_input_active,
#else
            false,
#endif
            block_delay,
            loop_bits,
            (const retro_keybind_set *)input_config_binds,
            binds_norm,
            binds_auto,
            joypad,
            sec_joypad,
            &joypad_info);

#ifdef HAVE_MENU
      if (menu_is_alive)
      {
         int j;
         /* Restores analog D-pad binds temporarily overridden. */
         for (j = RETRO_DEVICE_ID_JOYPAD_UP; j <= RETRO_DEVICE_ID_JOYPAD_RIGHT; j++)
         {
            (auto_binds)[j].joyaxis    = (auto_binds)[j].orig_joyaxis;
            (general_binds)[j].joyaxis = (general_binds)[j].orig_joyaxis;
         }
      }
#endif /* HAVE_MENU */

      /* we write port 0 directly to input_bits to save one iteration of this loop */
      if (port != 0)
      {
         int i;
         /* Update compound 'current_bits' record
          * Note: Only digital inputs are considered */
         for(i = 0; i < sizeof(current_bits->data) / sizeof(current_bits->data[0]); i++)
            current_bits->data[i] |= loop_bits->data[i];
      }
      else if (!all_users_control_menu)
         break;
   }

#ifdef HAVE_MENU
   if (menu_input_active)
   {
      /* Gather keyboard input, if enabled
       * Note: Keyboard input always read from
       * port 0 */
      if (     !display_kb
            &&  current_input->input_state)
      {
         unsigned i;
         unsigned ids[][2] =
         {
            {RETROK_SPACE,     RETRO_DEVICE_ID_JOYPAD_START   },
            {RETROK_SLASH,     RETRO_DEVICE_ID_JOYPAD_X       },
            {RETROK_RSHIFT,    RETRO_DEVICE_ID_JOYPAD_SELECT  },
            {RETROK_RIGHT,     RETRO_DEVICE_ID_JOYPAD_RIGHT   },
            {RETROK_LEFT,      RETRO_DEVICE_ID_JOYPAD_LEFT    },
            {RETROK_DOWN,      RETRO_DEVICE_ID_JOYPAD_DOWN    },
            {RETROK_UP,        RETRO_DEVICE_ID_JOYPAD_UP      },
            {RETROK_PAGEUP,    RETRO_DEVICE_ID_JOYPAD_L       },
            {RETROK_PAGEDOWN,  RETRO_DEVICE_ID_JOYPAD_R       },
            {0,                RARCH_QUIT_KEY                 },
            {0,                RARCH_FULLSCREEN_TOGGLE_KEY    },
            {RETROK_BACKSPACE, RETRO_DEVICE_ID_JOYPAD_B      },
            {RETROK_RETURN,    RETRO_DEVICE_ID_JOYPAD_A      },
            {RETROK_DELETE,    RETRO_DEVICE_ID_JOYPAD_Y      },
            {0,                RARCH_UI_COMPANION_TOGGLE     },
            {0,                RARCH_FPS_TOGGLE              },
            {0,                RARCH_NETPLAY_HOST_TOGGLE     },
            {0,                RARCH_MENU_TOGGLE             },
         };

         ids[9][0]  = input_config_binds[0][RARCH_QUIT_KEY].key;
         ids[10][0] = input_config_binds[0][RARCH_FULLSCREEN_TOGGLE_KEY].key;
         ids[14][0] = input_config_binds[0][RARCH_UI_COMPANION_TOGGLE].key;
         ids[15][0] = input_config_binds[0][RARCH_FPS_TOGGLE].key;
         ids[16][0] = input_config_binds[0][RARCH_NETPLAY_HOST_TOGGLE].key;
         ids[17][0] = input_config_binds[0][RARCH_MENU_TOGGLE].key;

         if (settings->bools.input_menu_swap_ok_cancel_buttons)
         {
            ids[11][1] = RETRO_DEVICE_ID_JOYPAD_A;
            ids[12][1] = RETRO_DEVICE_ID_JOYPAD_B;
         }

         for (i = 0; i < ARRAY_SIZE(ids); i++)
         {
            if (current_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)
   {
#if 0
      /* TODO/FIXME - overlaps with tilde */
      case '`':
         return "left quote";
#endif
      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_ctx_environment_t menu_environ;
         menu_environ.type           = MENU_ENVIRON_DISABLE_SCREENSAVER;
         menu_environ.data           = NULL;
         menu_st->flags             &= ~MENU_ST_FLAG_SCREENSAVER_ACTIVE;
         menu_st->input_last_time_us = menu_st->current_time_us;
         menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ);
      }
      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 = (char*)malloc(sizeof(char)+1);

         if (say_char)
         {
            char c      = (char)character;
            *say_char   = 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);
            }
            free(say_char);
         }
      }
   }
#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)
   {
      if (!down)
         return;

      switch (device)
      {
         case RETRO_DEVICE_POINTER:
            if (code != 0x12d)
               character = (char)code;
            /* fall-through */
         default:
            if (!input_keyboard_line_event(input_st,
                     &input_st->keyboard_line, character))
               return;
            break;
      }

      /* Line is complete, can free it now. */
      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.cb           = NULL;
      input_st->keyboard_line.userdata     = NULL;
      input_st->keyboard_line.enabled      = false;

      /* Unblock all hotkeys. */
      input_st->flags                     &= ~INP_FLAG_KB_MAPPING_BLOCKED;
   }
   else
   {
      if (code == RETROK_UNKNOWN)
         return;

      /* Block hotkey + RetroPad mapped keyboard key events,
       * but not with game focus, and from keyboard device type,
       * and with 'enable_hotkey' modifier set and unpressed */
      if (!input_st->game_focus_state.enabled &&
            BIT512_GET(input_st->keyboard_mapping_bits, code))
      {
         input_mapper_t *handle      = &input_st->mapper;
         struct retro_keybind hotkey = input_config_binds[0][RARCH_ENABLE_HOTKEY];
         bool hotkey_pressed         =
                  (input_st->input_hotkey_block_counter > 0)
               || (hotkey.key == code);

         if (!(MAPPER_GET_KEY(handle, code)) &&
               !(!hotkey_pressed && (
                     (hotkey.key     != RETROK_UNKNOWN)
                  || (hotkey.joykey  != NO_BTN)
                  || (hotkey.joyaxis != AXIS_NONE)
               )))
            return;
      }

      if (*key_event)
         (*key_event)(down, code, character, mod);
   }
}
