mirror of
https://github.com/libretro/RetroArch
synced 2025-02-15 00:40:06 +00:00
In some cases, set_sensor_state and get_sensor_input are more related to the joypad driver, e.g. in desktop platforms where sensors are associated rather with the joypad. If input driver supports the sensors, it is still preferred. Placeholder inserted for all input drivers, no functionality added yet.
578 lines
15 KiB
C
578 lines
15 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2011-2017 - Daniel De Matteis
|
|
* Copyright (C) 2014-2017 - Higor Euripedes
|
|
* Copyright (C) 2023 - Carlo Refice
|
|
*
|
|
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <compat/strl.h>
|
|
|
|
#include "SDL.h"
|
|
|
|
#include "../input_driver.h"
|
|
|
|
#include "../../tasks/tasks_internal.h"
|
|
#include "../../verbosity.h"
|
|
|
|
typedef struct _sdl_joypad
|
|
{
|
|
SDL_Joystick *joypad;
|
|
#ifdef HAVE_SDL2
|
|
SDL_GameController *controller;
|
|
SDL_Haptic *haptic;
|
|
int rumble_effect; /* -1 = not initialized, -2 = error/unsupported, -3 = use SDL_JoystickRumble instead of haptic */
|
|
#endif
|
|
unsigned num_axes;
|
|
unsigned num_buttons;
|
|
unsigned num_hats;
|
|
#ifdef HAVE_SDL2
|
|
unsigned num_balls;
|
|
#endif
|
|
} sdl_joypad_t;
|
|
|
|
/* TODO/FIXME - static globals */
|
|
static sdl_joypad_t sdl_pads[MAX_USERS];
|
|
#ifdef HAVE_SDL2
|
|
static bool g_has_haptic = false;
|
|
#endif
|
|
|
|
static const char *sdl_joypad_name(unsigned pad)
|
|
{
|
|
if (pad >= MAX_USERS)
|
|
return NULL;
|
|
|
|
#ifdef HAVE_SDL2
|
|
if (sdl_pads[pad].controller)
|
|
return SDL_GameControllerNameForIndex(pad);
|
|
return SDL_JoystickNameForIndex(pad);
|
|
#else
|
|
return SDL_JoystickName(pad);
|
|
#endif
|
|
}
|
|
|
|
static uint8_t sdl_pad_get_button(sdl_joypad_t *pad, unsigned button)
|
|
{
|
|
#ifdef HAVE_SDL2
|
|
/* TODO: see if a LUT like xinput_joypad.c's button_index_to_bitmap_code is needed. */
|
|
if (pad->controller)
|
|
return SDL_GameControllerGetButton(pad->controller, (SDL_GameControllerButton)button);
|
|
#endif
|
|
return SDL_JoystickGetButton(pad->joypad, button);
|
|
}
|
|
|
|
static uint8_t sdl_pad_get_hat(sdl_joypad_t *pad, unsigned hat)
|
|
{
|
|
#ifdef HAVE_SDL2
|
|
if (pad->controller)
|
|
return sdl_pad_get_button(pad, hat);
|
|
#endif
|
|
return SDL_JoystickGetHat(pad->joypad, hat);
|
|
}
|
|
|
|
static int16_t sdl_pad_get_axis(sdl_joypad_t *pad, unsigned axis)
|
|
{
|
|
#ifdef HAVE_SDL2
|
|
/* TODO: see if a rarch <-> sdl translation is needed. */
|
|
if (pad->controller)
|
|
return SDL_GameControllerGetAxis(pad->controller, (SDL_GameControllerAxis)axis);
|
|
#endif
|
|
return SDL_JoystickGetAxis(pad->joypad, axis);
|
|
}
|
|
|
|
static void sdl_pad_connect(unsigned id)
|
|
{
|
|
sdl_joypad_t *pad = (sdl_joypad_t*)&sdl_pads[id];
|
|
bool success = false;
|
|
int32_t product = 0;
|
|
int32_t vendor = 0;
|
|
|
|
#ifdef HAVE_SDL2
|
|
SDL_JoystickGUID guid;
|
|
uint16_t *guid_ptr = NULL;
|
|
|
|
if (SDL_IsGameController(id))
|
|
{
|
|
pad->controller = SDL_GameControllerOpen(id);
|
|
pad->joypad = SDL_GameControllerGetJoystick(pad->controller);
|
|
|
|
success = pad->joypad != NULL && pad->controller != NULL;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
pad->joypad = SDL_JoystickOpen(id);
|
|
success = pad->joypad != NULL;
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
RARCH_ERR("[SDL]: Couldn't open joystick #%u: %s.\n", id, SDL_GetError());
|
|
|
|
if (pad->joypad)
|
|
SDL_JoystickClose(pad->joypad);
|
|
|
|
pad->joypad = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
#ifdef HAVE_SDL2
|
|
guid = SDL_JoystickGetGUID(pad->joypad);
|
|
guid_ptr = (uint16_t*)guid.data;
|
|
#ifdef __linux
|
|
vendor = guid_ptr[2];
|
|
product = guid_ptr[4];
|
|
#elif _WIN32
|
|
vendor = guid_ptr[0];
|
|
product = guid_ptr[1];
|
|
#endif
|
|
#ifdef WEBOS
|
|
if (vendor == 0x9999 && product == 0x9999)
|
|
{
|
|
RARCH_WARN("[SDL_JOYPAD]: Ignoring pad #%d (vendor: %d; product: %d)\n", id, vendor, product);
|
|
if (pad->joypad)
|
|
SDL_JoystickClose(pad->joypad);
|
|
|
|
pad->joypad = NULL;
|
|
return;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
input_autoconfigure_connect(
|
|
sdl_joypad_name(id),
|
|
NULL,
|
|
sdl_joypad.ident,
|
|
id,
|
|
vendor,
|
|
product);
|
|
|
|
#ifdef HAVE_SDL2
|
|
if (pad->controller)
|
|
{
|
|
/* SDL_GameController internally supports all axis/button IDs, even if
|
|
* the controller's mapping does not have a binding for it.
|
|
*
|
|
* So, we can claim to support all axes/buttons, and when we try to poll
|
|
* an unbound ID, SDL simply returns the correct unpressed value.
|
|
*
|
|
* Note that, in addition to 0 trackballs, we also have 0 hats. This is
|
|
* because the d-pad is in the button list, as the last 4 enum entries.
|
|
*
|
|
* -flibit
|
|
*/
|
|
pad->num_axes = SDL_CONTROLLER_AXIS_MAX;
|
|
pad->num_buttons = SDL_CONTROLLER_BUTTON_MAX;
|
|
pad->num_hats = 0;
|
|
pad->num_balls = 0;
|
|
|
|
/* SDL Device supports Game Controller API. */
|
|
}
|
|
else
|
|
{
|
|
pad->num_axes = SDL_JoystickNumAxes(pad->joypad);
|
|
pad->num_buttons = SDL_JoystickNumButtons(pad->joypad);
|
|
pad->num_hats = SDL_JoystickNumHats(pad->joypad);
|
|
pad->num_balls = SDL_JoystickNumBalls(pad->joypad);
|
|
}
|
|
|
|
pad->haptic = NULL;
|
|
|
|
if (g_has_haptic)
|
|
{
|
|
pad->haptic = SDL_HapticOpenFromJoystick(pad->joypad);
|
|
|
|
if (!pad->haptic)
|
|
RARCH_WARN("[SDL]: Couldn't open haptic device of the joypad #%u: %s\n",
|
|
id, SDL_GetError());
|
|
}
|
|
|
|
pad->rumble_effect = -1;
|
|
|
|
if (pad->haptic)
|
|
{
|
|
SDL_HapticEffect efx;
|
|
efx.type = SDL_HAPTIC_LEFTRIGHT;
|
|
efx.leftright.type = SDL_HAPTIC_LEFTRIGHT;
|
|
efx.leftright.large_magnitude = efx.leftright.small_magnitude = 0x4000;
|
|
efx.leftright.length = 5000;
|
|
|
|
if (SDL_HapticEffectSupported(pad->haptic, &efx) == SDL_FALSE)
|
|
{
|
|
pad->rumble_effect = -2;
|
|
RARCH_WARN("[SDL]: Device #%u does not support leftright haptic effect.\n", id);
|
|
}
|
|
}
|
|
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
|
if (!pad->haptic || pad->rumble_effect == -2) {
|
|
pad->rumble_effect = -3;
|
|
RARCH_LOG("[SDL]: Falling back to joystick rumble\n");
|
|
}
|
|
#endif
|
|
#else
|
|
pad->num_axes = SDL_JoystickNumAxes(pad->joypad);
|
|
pad->num_buttons = SDL_JoystickNumButtons(pad->joypad);
|
|
pad->num_hats = SDL_JoystickNumHats(pad->joypad);
|
|
#endif
|
|
}
|
|
|
|
static void sdl_pad_disconnect(unsigned id)
|
|
{
|
|
#ifdef HAVE_SDL2
|
|
if (sdl_pads[id].haptic)
|
|
SDL_HapticClose(sdl_pads[id].haptic);
|
|
|
|
if (sdl_pads[id].controller)
|
|
{
|
|
SDL_GameControllerClose(sdl_pads[id].controller);
|
|
input_autoconfigure_disconnect(id, sdl_joypad.ident);
|
|
}
|
|
else
|
|
#endif
|
|
if (sdl_pads[id].joypad)
|
|
{
|
|
SDL_JoystickClose(sdl_pads[id].joypad);
|
|
input_autoconfigure_disconnect(id, sdl_joypad.ident);
|
|
}
|
|
|
|
memset(&sdl_pads[id], 0, sizeof(sdl_pads[id]));
|
|
}
|
|
|
|
static void sdl_joypad_destroy(void)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < MAX_USERS; i++)
|
|
sdl_pad_disconnect(i);
|
|
|
|
memset(sdl_pads, 0, sizeof(sdl_pads));
|
|
}
|
|
|
|
static void *sdl_joypad_init(void *data)
|
|
{
|
|
unsigned i, num_sticks;
|
|
#ifdef HAVE_SDL2
|
|
uint32_t subsystem = SDL_INIT_GAMECONTROLLER;
|
|
#else
|
|
uint32_t subsystem = SDL_INIT_JOYSTICK;
|
|
#endif
|
|
uint32_t sdl_subsystem_flags = SDL_WasInit(0);
|
|
|
|
/* Initialise joystick/controller subsystem, if required */
|
|
if (sdl_subsystem_flags == 0)
|
|
{
|
|
if (SDL_Init(subsystem) < 0)
|
|
return NULL;
|
|
}
|
|
else if ((sdl_subsystem_flags & subsystem) == 0)
|
|
{
|
|
if (SDL_InitSubSystem(subsystem) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
#if HAVE_SDL2
|
|
g_has_haptic = false;
|
|
|
|
/* Initialise haptic subsystem, if required */
|
|
if ((sdl_subsystem_flags & SDL_INIT_HAPTIC) == 0)
|
|
{
|
|
if (SDL_InitSubSystem(SDL_INIT_HAPTIC) < 0)
|
|
RARCH_WARN("[SDL]: Failed to initialize haptic device support: %s\n",
|
|
SDL_GetError());
|
|
else
|
|
g_has_haptic = true;
|
|
}
|
|
else
|
|
g_has_haptic = true;
|
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
|
/* enable extended hid reports to support ps4/ps5 rumble over bluetooth */
|
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
|
#endif
|
|
#endif
|
|
|
|
memset(sdl_pads, 0, sizeof(sdl_pads));
|
|
|
|
num_sticks = SDL_NumJoysticks();
|
|
if (num_sticks > MAX_USERS)
|
|
num_sticks = MAX_USERS;
|
|
|
|
for (i = 0; i < num_sticks; i++)
|
|
sdl_pad_connect(i);
|
|
|
|
#ifndef HAVE_SDL2
|
|
/* quit if no joypad is detected. */
|
|
num_sticks = 0;
|
|
for (i = 0; i < MAX_USERS; i++)
|
|
if (sdl_pads[i].joypad)
|
|
num_sticks++;
|
|
|
|
if (num_sticks == 0)
|
|
goto error;
|
|
#endif
|
|
|
|
return (void*)-1;
|
|
|
|
#ifndef HAVE_SDL2
|
|
error:
|
|
sdl_joypad_destroy();
|
|
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
static int32_t sdl_joypad_button_state(
|
|
sdl_joypad_t *pad,
|
|
unsigned port, uint16_t joykey)
|
|
{
|
|
unsigned hat_dir = GET_HAT_DIR(joykey);
|
|
/* Check hat. */
|
|
if (hat_dir)
|
|
{
|
|
uint8_t dir;
|
|
uint16_t hat = GET_HAT(joykey);
|
|
|
|
if (hat >= pad->num_hats)
|
|
return 0;
|
|
|
|
dir = sdl_pad_get_hat(pad, hat);
|
|
|
|
switch (hat_dir)
|
|
{
|
|
case HAT_UP_MASK:
|
|
return (dir & SDL_HAT_UP);
|
|
case HAT_DOWN_MASK:
|
|
return (dir & SDL_HAT_DOWN);
|
|
case HAT_LEFT_MASK:
|
|
return (dir & SDL_HAT_LEFT);
|
|
case HAT_RIGHT_MASK:
|
|
return (dir & SDL_HAT_RIGHT);
|
|
default:
|
|
break;
|
|
}
|
|
/* hat requested and no hat button down */
|
|
}
|
|
else if (joykey < pad->num_buttons)
|
|
return sdl_pad_get_button(pad, joykey);
|
|
return 0;
|
|
}
|
|
|
|
static int32_t sdl_joypad_button(unsigned port, uint16_t joykey)
|
|
{
|
|
sdl_joypad_t *pad = (sdl_joypad_t*)&sdl_pads[port];
|
|
if (!pad || !pad->joypad)
|
|
return 0;
|
|
if (port >= MAX_USERS)
|
|
return 0;
|
|
return sdl_joypad_button_state(pad, port, joykey);
|
|
}
|
|
|
|
static int16_t sdl_joypad_axis_state(
|
|
sdl_joypad_t *pad,
|
|
unsigned port, uint32_t joyaxis)
|
|
{
|
|
if (AXIS_NEG_GET(joyaxis) < pad->num_axes)
|
|
{
|
|
int16_t val = sdl_pad_get_axis(pad, AXIS_NEG_GET(joyaxis));
|
|
if (val < 0)
|
|
{
|
|
/* Clamp - -0x8000 can cause trouble if we later abs() it. */
|
|
if (val < -0x7fff)
|
|
return -0x7fff;
|
|
return val;
|
|
}
|
|
}
|
|
else if (AXIS_POS_GET(joyaxis) < pad->num_axes)
|
|
{
|
|
int16_t val = sdl_pad_get_axis(pad, AXIS_POS_GET(joyaxis));
|
|
if (val > 0)
|
|
return val;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int16_t sdl_joypad_axis(unsigned port, uint32_t joyaxis)
|
|
{
|
|
sdl_joypad_t *pad = (sdl_joypad_t*)&sdl_pads[port];
|
|
if (!pad || !pad->joypad)
|
|
return false;
|
|
return sdl_joypad_axis_state(pad, port, joyaxis);
|
|
}
|
|
|
|
static int16_t sdl_joypad_state(
|
|
rarch_joypad_info_t *joypad_info,
|
|
const struct retro_keybind *binds,
|
|
unsigned port)
|
|
{
|
|
unsigned i;
|
|
int16_t ret = 0;
|
|
uint16_t port_idx = joypad_info->joy_idx;
|
|
sdl_joypad_t *pad = (sdl_joypad_t*)&sdl_pads[port_idx];
|
|
|
|
if (!pad || !pad->joypad)
|
|
return 0;
|
|
if (port_idx >= MAX_USERS)
|
|
return 0;
|
|
|
|
for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
|
|
{
|
|
/* Auto-binds are per joypad, not per user. */
|
|
const uint64_t joykey = (binds[i].joykey != NO_BTN)
|
|
? binds[i].joykey : joypad_info->auto_binds[i].joykey;
|
|
const uint32_t joyaxis = (binds[i].joyaxis != AXIS_NONE)
|
|
? binds[i].joyaxis : joypad_info->auto_binds[i].joyaxis;
|
|
if (
|
|
(uint16_t)joykey != NO_BTN
|
|
&& sdl_joypad_button_state(pad, port_idx, (uint16_t)joykey)
|
|
)
|
|
ret |= ( 1 << i);
|
|
else if (joyaxis != AXIS_NONE &&
|
|
((float)abs(sdl_joypad_axis_state(pad, port_idx, joyaxis))
|
|
/ 0x8000) > joypad_info->axis_threshold)
|
|
ret |= (1 << i);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void sdl_joypad_poll(void)
|
|
{
|
|
#ifdef HAVE_SDL2
|
|
SDL_Event event;
|
|
|
|
SDL_PumpEvents();
|
|
|
|
while (SDL_PeepEvents(&event, 1,
|
|
SDL_GETEVENT, SDL_JOYDEVICEADDED, SDL_JOYDEVICEREMOVED) > 0)
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case SDL_JOYDEVICEADDED:
|
|
sdl_pad_connect(event.jdevice.which);
|
|
break;
|
|
case SDL_JOYDEVICEREMOVED:
|
|
sdl_pad_disconnect(event.jdevice.which);
|
|
break;
|
|
}
|
|
}
|
|
|
|
SDL_FlushEvents(SDL_JOYAXISMOTION, SDL_CONTROLLERDEVICEREMAPPED);
|
|
#else
|
|
SDL_JoystickUpdate();
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_SDL2
|
|
static bool sdl_joypad_set_rumble(unsigned pad, enum retro_rumble_effect effect, uint16_t strength)
|
|
{
|
|
SDL_HapticEffect efx;
|
|
sdl_joypad_t *joypad = (sdl_joypad_t*)&sdl_pads[pad];
|
|
|
|
memset(&efx, 0, sizeof(efx));
|
|
|
|
if (!joypad->joypad)
|
|
return false;
|
|
|
|
efx.type = SDL_HAPTIC_LEFTRIGHT;
|
|
efx.leftright.type = SDL_HAPTIC_LEFTRIGHT;
|
|
efx.leftright.length = 5000;
|
|
|
|
switch (effect)
|
|
{
|
|
case RETRO_RUMBLE_STRONG:
|
|
efx.leftright.large_magnitude = strength;
|
|
break;
|
|
case RETRO_RUMBLE_WEAK:
|
|
efx.leftright.small_magnitude = strength;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
|
if (joypad->rumble_effect == -3)
|
|
{
|
|
if (SDL_JoystickRumble(joypad->joypad, efx.leftright.large_magnitude, efx.leftright.small_magnitude, efx.leftright.length) == -1)
|
|
{
|
|
RARCH_WARN("[SDL]: Failed to rumble joypad %u: %s\n",
|
|
pad, SDL_GetError());
|
|
joypad->rumble_effect = -2;
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!joypad->haptic)
|
|
return false;
|
|
|
|
if (joypad->rumble_effect == -1)
|
|
{
|
|
joypad->rumble_effect = SDL_HapticNewEffect(joypad->haptic, &efx);
|
|
if (joypad->rumble_effect < 0)
|
|
{
|
|
RARCH_WARN("[SDL]: Failed to create rumble effect for joypad %u: %s\n",
|
|
pad, SDL_GetError());
|
|
joypad->rumble_effect = -2;
|
|
return false;
|
|
}
|
|
}
|
|
else if (joypad->rumble_effect >= 0)
|
|
SDL_HapticUpdateEffect(joypad->haptic, joypad->rumble_effect, &efx);
|
|
|
|
if (joypad->rumble_effect < 0)
|
|
return false;
|
|
|
|
if (SDL_HapticRunEffect(joypad->haptic, joypad->rumble_effect, 1) < 0)
|
|
{
|
|
RARCH_WARN("[SDL]: Failed to set rumble effect on joypad %u: %s\n",
|
|
pad, SDL_GetError());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static bool sdl_joypad_query_pad(unsigned pad)
|
|
{
|
|
return pad < MAX_USERS && sdl_pads[pad].joypad;
|
|
}
|
|
|
|
input_device_driver_t sdl_joypad = {
|
|
sdl_joypad_init,
|
|
sdl_joypad_query_pad,
|
|
sdl_joypad_destroy,
|
|
sdl_joypad_button,
|
|
sdl_joypad_state,
|
|
NULL,
|
|
sdl_joypad_axis,
|
|
sdl_joypad_poll,
|
|
#ifdef HAVE_SDL2
|
|
sdl_joypad_set_rumble,
|
|
#else
|
|
NULL, /* set_rumble */
|
|
#endif
|
|
NULL, /* set_rumble_gain */
|
|
NULL, /* set_sensor_state */
|
|
NULL, /* get_sensor_input */
|
|
sdl_joypad_name,
|
|
#ifdef HAVE_SDL2
|
|
"sdl2",
|
|
#else
|
|
"sdl"
|
|
#endif
|
|
};
|